PDFTron full api is not getting loaded in WebViewer - pdf

I am trying to get the full api of PDFTron working from the WEbViewer. I followed the steps in the link below.
https://www.pdftron.com/documentation/web/guides/full-api/setup/
But I am getting the error given below in console while loading the webviewer.
Uncaught (in promise) Error: Full version of PDFNetJS has not been loaded. Please pass the "fullAPI: true" option in your WebViewer constructor to use the PDFNet APIs.
at Object.get (CoreControls.js:1694)
at z.docViewer.on ((index):43)
at CoreControls.js:398
at Array.forEach (<anonymous>)
at z.O (CoreControls.js:398)
at CoreControls.js:213
This is my code.
WebViewer({
path: 'WebViewer-6.0.2/lib', // path to the PDFTron 'lib' folder on your server
type: 'html5',
initialDoc: 'forms/local.pdf', // You can also use documents on your server
fullAPI: true,
}, document.getElementById('viewer'))
.then(instance => {
const docViewer = instance.docViewer;
const annotManager = instance.annotManager;
const Annotations = instance.Annotations;
Annotations.ChoiceWidgetAnnotation.FORCE_SELECT=true;
const Actions = instance.Actions;
docViewer.on('documentLoaded', async () => {
const PDFNet = instance.PDFNet;
await PDFNet.Initialize();
// This part requires the full API: https://www.pdftron.com/documentation/web/guides/full-api/setup/
alert('async');
const doc = docViewer.getDocument();
// Get document from worker
const pdfDoc = await doc.getPDFDoc();
pdfDoc.getAcroForm().putBool("NeedAppearances", true);
});
docViewer.on('documentLoaded', () => {
docViewer.on('annotationsLoaded', () => {
const annotations = annotManager.getAnnotationsList();
annotations.forEach(annot => {
console.log('fieldName => '+annot.fieldName);
});
});
Please help me resolve this.
EDIT
Modified the code as suggested by #Andy.
The updated code in index.html file looks like below,
<!DOCTYPE html>
<html>
<head>
<title>Basic WebViewer</title>
</head>
<!-- Import WebViewer as a script tag -->
<script src='WebViewer-6.0.2/lib/webviewer.min.js'></script>
<body>
<div id='viewer' style='width: 1024px; height: 600px; margin: 0 auto;'>
<script>
WebViewer({
path: 'WebViewer-6.0.2/lib', // path to the PDFTron 'lib' folder on your server
type: 'html5',
fullAPI: true,
// licenseKey: 'Insert commercial license key here after purchase',
}, document.getElementById('viewer'))
.then(async instance => {
const { Annotations, Tools, CoreControls, PDFNet, PartRetrievers, docViewer, annotManager } = instance;
await PDFNet.Initialize();
Annotations.ChoiceWidgetAnnotation.FORCE_SELECT=true;
const Actions = instance.Actions;
docViewer.on('documentLoaded', async () => {
// This part requires the full API: https://www.pdftron.com/documentation/web/guides/full-api/setup/
const doc = docViewer.getDocument();
// Get document from worker
const pdfDoc = await doc.getPDFDoc();
const acroFrom = await pdfDoc.getAcroForm();
acroform.putBool("NeedAppearances", true);
});
instance.loadDocument('forms/test.pdf');
});
</script>
</div>
</body>
</html>
I am loading the file from a http server in my project folder.
http-server -a localhost -p 7080
Unfortunately, I am getting the same error.
Error: Full version of PDFNetJS has not been loaded. Please pass the "fullAPI: true" option in your WebViewer constructor to use the PDFNet APIs.
We are currently evaluating PDFTron, so the licenseKey option is not passed in the WebViewer constructor.
Kindly help me on this.

I have tried out the code you have provided and was still not able to reproduce the issue you are encountering. I do typically perform the initialize outside WebViewer events so the initialization occurs only once:
WebViewer(...)
.then(instance => {
const { Annotations, Tools, CoreControls, PDFNet, PartRetrievers, docViewer } = instance;
const annotManager = docViewer.getAnnotationManager();
await PDFNet.initialize(); // Only needs to be initialized once
docViewer.on('documentLoaded', ...);
docViewer.on('annotationsLoaded', ...);
});
Also, I noticed that you attach an an event handler to annotationsLoaded every time documentLoaded is triggered. I am not sure if that is intentional or desirable but this can lead to the handler triggering multiple times (when switching documents).
This may not matter but instead of using initialDoc, you could try instance.loadDocument after the initialize instead.
await PDFNet.initialize();
docViewer.on('documentLoaded', ...);
docViewer.on('annotationsLoaded', ...);
instance.loadDocument('http://...');
There is one last thing to mention about the full API. The APIs will return a promise most of the time as the result so you will have to await the return value most of the time.
const acroFrom = await pdfDoc.getAcroForm();
// You can await this too. Especially if you need a reference to the new bool object that was
acroform.putBool("NeedAppearances", true);
Let me know if this helps!

Related

Why Vercel Web vitals is not loading for my Astro Project?

I created a landing page using Astro with Tailwind CSS. And it is currently hosted on Vercel. I wanted to try out the analytics service provided by Vercel. I have been able to avail the Audience analytics service provided by Vercel. However, I cannot avail the web vitals services. After enabling the service and redeploying my project, I am stuck in this screen (screen shot provided).
Please note that I did turn off the ad blocker but that did not resolve the issue.I also added the following meta tag to resolve any CSP issue
<meta http-equiv="Content-Security-Policy"
content="default-src 'self' vitals.vercel-insights.com"/>
But that has not solved the problem.That is why I want to know does Vercel support analytics for Astro projects and if they do, then what am I doing wrong? Thank you.
Vercel’s Web Vitals analytics currently only has out-of-the-box support for Next, Nuxt, and Gatsby.
To track Web Vitals with a different framework like Astro, you need a bit of manual set up work as documented in Vercel’s Web Vitals API docs.
For example in your base Astro layout you could include a script tag that will import their example code and run it:
---
// src/layouts/BaseLayout.astro
---
<script>
import { webVitals } from '../scripts/vitals';
const analyticsId = import.meta.env.PUBLIC_VERCEL_ANALYTICS_ID;
webVitals({
path: window.location.pathname,
analyticsId,
});
</script>
Here’s Vercel’s example vitals.js snippet:
// src/scripts/vitals.js
import { getCLS, getFCP, getFID, getLCP, getTTFB } from 'web-vitals';
const vitalsUrl = 'https://vitals.vercel-analytics.com/v1/vitals';
function getConnectionSpeed() {
return 'connection' in navigator &&
navigator['connection'] &&
'effectiveType' in navigator['connection']
? navigator['connection']['effectiveType']
: '';
}
function sendToAnalytics(metric, options) {
const body = {
dsn: options.analyticsId, // qPgJqYH9LQX5o31Ormk8iWhCxZO
id: metric.id, // v2-1653884975443-1839479248192
page: options.path, // /blog/my-test
href: location.href, // https://my-app.vercel.app/blog/my-test
event_name: metric.name, // TTFB
value: metric.value.toString(), // 60.20000000298023
speed: getConnectionSpeed(), // 4g
};
if (options.debug) {
console.log('[Analytics]', metric.name, JSON.stringify(body, null, 2));
}
const blob = new Blob([new URLSearchParams(body).toString()], {
// This content type is necessary for `sendBeacon`
type: 'application/x-www-form-urlencoded',
});
if (navigator.sendBeacon) {
navigator.sendBeacon(vitalsUrl, blob);
} else
fetch(vitalsUrl, {
body: blob,
method: 'POST',
credentials: 'omit',
keepalive: true,
});
}
export function webVitals(options) {
try {
getFID((metric) => sendToAnalytics(metric, options));
getTTFB((metric) => sendToAnalytics(metric, options));
getLCP((metric) => sendToAnalytics(metric, options));
getCLS((metric) => sendToAnalytics(metric, options));
getFCP((metric) => sendToAnalytics(metric, options));
} catch (err) {
console.error('[Analytics]', err);
}
}
For a slightly more real-world implementation you, check out the <TrackVitals> Astro component in the astro-badge repo.
Vercel analytics has support for frameworks other than Next, Nuxt Gatsby etc. The way to achieve it in Astro (1.6, 2.0 etc.) is to install the #vercel/analytics package and inject a simple <script> tag that imports it and calls its exported function inject():
<script>
import { inject } from '#vercel/analytics'
// #ts-ignore: process.env.NODE_ENV is required by #vercel/analytics internally
// so that it can determine the correct path for importing the analytics script
globalThis.process = { env: { NODE_ENV: import.meta.env.MODE } }
inject()
</script>
You can inject this code in your <head> section in any .astro template file.
Unfortunately, the package is expecting a non-ESM runtime environment and is internally conditionally checking for process.env.NODE_ENV to determine which script to load (local-relative path to JS or from a remote host, fully qualified domain name). This is the reason, the MODE needs to be exposed as process.env.NODE_ENV. I tried to achieve this via Vite using define, but Astro seems to check for process somewhere else internally and fails.

TestCafe: Changing download location

I am trying to change the download location and I found these codes doing research (sorry I forgot where I got these)
const browserConnection = t.testRun.browserConnection;
const client = browserConnection.provider.plugin.openedBrowsers[browserConnection.id].client;
const { Network, Page } = client;
const downloadDirectory = '../my_downloads');
await Promise.all([
Network.enable(),
Page.enable()
]);
Network.requestWillBeSent((param) => {
// console.log("Network.requestWillBeSent: " + JSON.stringify(param));
});
Network.responseReceived((param) => {
// console.log("Network.responseReceived: " + JSON.stringify(param));
});
await Page.setDownloadBehavior({
behavior: 'allow',
downloadPath: downloadDirectory
});
It was working perfectly fine using version 10.9.2 and this version was installed globally. I updated my TestCafe to 1.10.1 locally installed and now got this error:
TypeError: Cannot destructure property 'Network' of 'client' as it is undefined.
Any inputs are well appreciated. And looking forward to it :)
This internal API has changed due to testing support of multiple windows. Please use the getActiveClient method:
const browserConnection = t.testRun.browserConnection;
const runtimeInfo = rowserConnection.provider.plugin.openedBrowsers[browserConnection.id];
const { Network, Page } = await runtimeInfo.browserClient.getActiveClient();
If you need to change the download location to read and check a file from it, please use a public API for this: example.

Input form provides File - how to I upload it to Azure Blob storage using Vue?

I'm clearly missing something here so forgive me - all examples seem to involve express and I don't have express in my setup. I am using Vue.js.
Ultimately, want my client-side Vue app to be able to upload any file to azure blob storage.
I have the file(File api) from my Vue form. However, it does not provide a path (I believe this is for security reasons). The Azure docs have this snippet example:
const uploadLocalFile = async (containerName, filePath) => {
return new Promise((resolve, reject) => {
const fullPath = path.resolve(filePath);
const blobName = path.basename(filePath);
blobService.createBlockBlobFromLocalFile(containerName, blobName, fullPath, err => {
if (err) {
reject(err);
} else {
resolve({ message: `Local file "${filePath}" is uploaded` });
}
});
});
};
Is this not the api I should be using? What should I be doing to upload any type of blob to blob storage?
UPDATE
Following #Adam Smith-MSFT comments below I have tried the vue-azure-storage-upload but can't seem to get the files up to azure.
startUpload () {
if (!this.files || !this.baseUrl) {
window.alert('Provide proper data first!')
} else {
this.files.forEach((file:File) => {
this.$azureUpload({
baseUrl: this.baseUrl + file.name,
sasToken: this.sasToken,
file: file,
progress: this.onProgress,
complete: this.onComplete,
error: this.onError
// blockSize
})
})
}
},
According to the console the response.data is undefined and when the onError method fires, that too gives me an undefined event.
I'd highly recommend checking the following tutorial: https://www.npmjs.com/package/vue-azure-blob-upload
The author used a specific npm package to upload blobs(you can using file service) to upload objects:
npm i --save vue-azure-blob-upload
I'd also recommend checking the Storage JS documentation: https://github.com/Azure/azure-storage-js/tree/master/file , it provides specific examples related to Azure File Storage as well.

How to cache .mp4 files in Safari with workbox-webpack-plugin?

I'm having exactly the same issue reported at https://github.com/GoogleChrome/workbox/issues/1663 which describes an issue that occurs exclusively in Safari where mp4 videos are not rendered after being cached by the service worker.
I'm using workbox-webpack-plugin, so the instructions provided in the comment https://github.com/GoogleChrome/workbox/issues/1663#issuecomment-448755945 will not work in my case. I'm not being able to require workbox-range-requests plugin in my webpack config file and pass it to the runtime caching options because I believe this package is intended for browser usage only. My workbox config is precaching .mp4 assets and uses a network first strategy for runtime caching.
How can I setup workbox-range-requests with workbox-webpack-plugin?
EDIT: Following Jeff's answer below, I've adjusted my webpack config to the following:
new WorkboxPlugin.InjectManifest({
swSrc: serviceWorkerSrcPath,
swDest: serviceWorkerBuildPath,
importsDirectory: 'sw',
})
The build produces the following service worker:
importScripts("/_build/sw/precache-manifest.8a0be820b796b153c97ba206d9753bdb.js", "https://storage.googleapis.com/workbox-cdn/releases/3.6.2/workbox-sw.js");
workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
workbox.routing.registerRoute(
/.*\.mp4/,
new workbox.strategies.CacheFirst({
cacheName: 'videos',
plugins: [
new workbox.cacheableResponse.Plugin({ statuses: [200] }),
new workbox.rangeRequests.Plugin(),
],
}),
);
If forgot to mention previously, but I've also added crossOrigin="anonymous" attribute to the video elements.
EDIT:
Repro that demonstrates it does not work as expected on Safari: https://github.com/acostalima/workbox-range-requests-mp4-demo
There's specific guidance for this use case in the "Serve cached audio and video" recipe in the Workbox documentation.
You can continue using the workbox-webpack-plugin, but I'd suggest using it in InjectManifest mode, which will give you control over the top-level service worker file. That will in turn make it possible to follow the recipe.
This documentation has guidance on configuring workbox-webpack-plugin in InjectManifest mode.
I had the same issue with Safari and managed to resolve it by removing my video from the precahe list self.__precacheManifest and instead by adding it in the service worker's install handler:
self.addEventListener('install', (event) => {
const urls = [/* videoUrl */];
const cacheName = 'videos';
event.waitUntil(caches.open(cacheName).then((cache) => cache.addAll(urls)));
});
Looking at the logs, it seemed that otherwise only the precache was used to respond to the request for the video resource and not the router.
Although the docs say that adding mp4s to the precache cache and then configuring the range plugin to handle precache mp4s is supposed to work, in practice, it wasn't. Removing mp4s from the precache and configuring your own video cache with the range plugin did the trick for me. Don't forget to add the crossorigin="anonymous" tag to your videos!
Here's how I did it (webpack 5, workbox 6):
// src/service-worker.js
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
import { cacheNames } from 'workbox-core';
import { precacheAndRoute } from 'workbox-precaching';
import { RangeRequestsPlugin } from 'workbox-range-requests';
import { registerRoute } from 'workbox-routing';
import { CacheFirst } from 'workbox-strategies';
const allEntries = self.__WB_MANIFEST; // Injected by WorkboxWebpackPlugin at compile time
const videoEntries = allEntries.filter((entry) => entry.url.endsWith('.mp4'));
const restEntries = allEntries.filter((entry) => !entry.url.endsWith('.mp4'));
precacheAndRoute(restEntries);
const videoCacheName = `${cacheNames.prefix}-videos-${cacheNames.suffix}`;
self.addEventListener('install', (event) => {
const allVideosAddedToCache = caches.open(videoCacheName).then((videoCache) => {
const videoUrls = videoEntries.map((entry) => entry.url);
return videoCache.addAll(videoUrls);
});
event.waitUntil(allVideosAddedToCache);
});
registerRoute(
(route) => route.url.pathname.endsWith('.mp4'),
new CacheFirst({
cacheName: videoCacheName,
plugins: [new CacheableResponsePlugin({ statuses: [200] }), new RangeRequestsPlugin()],
})
);
// webpack.config.js
plugins: [
new WorkboxWebpackPlugin.InjectManifest({
swSrc: 'src/service-worker.js',
}),
]
// index.tsx
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js');
});
}

Pdf2json integrated with Protactor

Has someone integrated pdf2json npm package with Protractor? I have been able to create a standalone node application to convert a PDF to json.
What I'm trying to do now is to add pdf2json to protractor.config.js and be able to use it in my test specs.
I managed to make it work myself so I thought to post what I did just in case someone needs the same.
Add the following to the Protractor config file
// PDF Parser
var PDFParser = require("pdf2json");
global.pdfParser = new PDFParser();
In the spec, we just need to wait for the async call to load the PDF to finish - note the done() (see Jasmine Async Support). The spec would look like:
var fs = require('fs');
describe('PDF Parser', function() {
it ("The spec", function(done){
// Capture the error
pdfParser.on("pdfParser_dataError", errData => {
console.error(errData);
done();
});
// Transform to json
pdfParser.on("pdfParser_dataReady", pdfData => {
fs.writeFile("path/to/save/json/file", JSON.stringify(pdfData));
done();
});
// This is an async call. We have to wait for it, so we use done in the 'it'
pdfParser.loadPDF("path/to/pdf/file");
});
});