From be3ba98562134fab44369815ab7e81f0766e1828 Mon Sep 17 00:00:00 2001
From: Stefano <valenzano.stefano99@gmail.com>
Date: Sat, 11 Jan 2025 15:24:40 +0100
Subject: [PATCH] 1.0.9 - error handling, update docs, new tests, fix types
 export. Update app refs when document is opened form pdf.js

---
 .gitignore                                |   5 +-
 docs/.vitepress/config.ts                 |   4 +
 docs/components/PdfViewer.vue             |  26 ++-
 docs/examples/basic-examples.md           | 217 ++++++++++++++++++++++
 docs/index.md                             |  12 +-
 packages/vue-pdfjs-playground/src/App.vue |  43 ++++-
 packages/vue/README.md                    | 104 ++++++-----
 packages/vue/package.json                 |   2 +-
 packages/vue/src/components/VuePDFjs.vue  |  86 ++++++---
 packages/vue/src/index.ts                 |  20 +-
 packages/vue/src/scripts/utils.ts         |  33 +++-
 packages/vue/src/types.ts                 |  59 +++---
 packages/vue/tests/mount.test.ts          |   7 +-
 packages/vue/tests/options.test.ts        |  63 ++++++-
 packages/vue/tests/utils.test.ts          |  48 +++++
 15 files changed, 594 insertions(+), 135 deletions(-)
 create mode 100644 packages/vue/tests/utils.test.ts

diff --git a/.gitignore b/.gitignore
index 3f3b368..12a094c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -176,4 +176,7 @@ dist
 
 # Vitepress
 docs/.vitepress/cache
-docs/.vitepress/dist
\ No newline at end of file
+docs/.vitepress/dist
+
+# In any tests directory ignore __screenshots__ directory
+__screenshots__/
\ No newline at end of file
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index d91727c..e50cd79 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -47,6 +47,10 @@ export default defineConfig({
 
     socialLinks: [
       { icon: "github", link: "https://github.com/tuttarealstep/vue-pdf.js" },
+      {
+        icon: "npm",
+        link: "https://www.npmjs.com/package/@tuttarealstep/vue-pdf.js",
+      },
     ],
   },
 });
diff --git a/docs/components/PdfViewer.vue b/docs/components/PdfViewer.vue
index d2379d1..a2beea3 100644
--- a/docs/components/PdfViewer.vue
+++ b/docs/components/PdfViewer.vue
@@ -27,6 +27,15 @@ const options = reactive<NonNullable<VuePDFjsProps['options']>>({
     }
 })
 
+const onErrorHandler = (error: Error) => {
+    console.error('Error loading PDF:', error)
+    alert('An error occurred with the PDF')
+}
+
+const sourceOptions = {
+    onError: onErrorHandler
+}
+
 watchEffect(() => {
     if (options.toolbar) {
         options.toolbar.visible = !hideToolbar.value
@@ -62,6 +71,8 @@ const onPdfAppLoaded = () => {
         //Set the number of pages in the ref
         pages.value = e.pagesCount
     })
+
+    vuepdfjs.value.pdfApp.eventBus.on('documenterror', onErrorHandler)
 }
 
 const pdf = 'https://raw.githubusercontent.com/mozilla/pdf.js/v4.10.38/web/compressed.tracemonkey-pldi-09.pdf'
@@ -73,8 +84,12 @@ const pdf = 'https://raw.githubusercontent.com/mozilla/pdf.js/v4.10.38/web/compr
     <div>
         <input type="checkbox" v-model="hideToolbar" /> Hide Toolbar
         <input type="checkbox" v-model="hideSidebar" /> Hide Sidebar
+        <button type="button" class="custom-button">
+            Load Invalid PDF
+        </button>
     </div>
-    <VuePDFjs ref="vuepdfjs" :source="pdf" :options="options" @pdf-app:loaded="onPdfAppLoaded" />
+    <VuePDFjs ref="vuepdfjs" :source="pdf" :options="options" :source-options="sourceOptions"
+        @pdf-app:loaded="onPdfAppLoaded" />
 </template>
 
 <style>
@@ -85,4 +100,13 @@ const pdf = 'https://raw.githubusercontent.com/mozilla/pdf.js/v4.10.38/web/compr
     display: flex;
     flex-direction: column;
 }
+
+.custom-button {
+    padding: 0.25rem 0.75rem;
+    background-color: #007bff;
+    color: white;
+    border: none;
+    border-radius: 5px;
+    cursor: pointer;
+}
 </style>
\ No newline at end of file
diff --git a/docs/examples/basic-examples.md b/docs/examples/basic-examples.md
index bfea340..d94ab91 100644
--- a/docs/examples/basic-examples.md
+++ b/docs/examples/basic-examples.md
@@ -127,3 +127,220 @@ body {
 }
 </style>
 ```
+
+## Handle errors
+
+### Handle errors with the `usePDF` composable
+
+In this example, we handle errors in the `usePDF` composable and display a message to the user.
+
+```vue
+<script setup lang="ts">
+import { reactive, useTemplateRef } from "vue";
+import {
+  VuePDFjs,
+  usePDF,
+  type VuePDFjsProps,
+} from "@tuttarealstep/vue-pdf.js";
+import "@tuttarealstep/vue-pdf.js/dist/style.css";
+import enUS_FTL from "@tuttarealstep/vue-pdf.js/l10n/en-US/viewer.ftl?raw";
+
+const vuepdfjs = useTemplateRef<typeof VuePDFjs>("vuepdfjs");
+
+const options = reactive<NonNullable<VuePDFjsProps["options"]>>({
+  locale: {
+    code: "en-US",
+    ftl: enUS_FTL,
+  },
+});
+
+const {
+  pdf: document,
+  info,
+  pages,
+} = usePDF("invalid-source", {
+  onError: (error) => {
+    console.error("Error loading the PDF file", error);
+    alert("Error loading the PDF file");
+  },
+});
+
+console.log(document, info, pages);
+</script>
+
+<template>
+  <div id="app">
+    <VuePDFjs ref="vuepdfjs" :source="document" :options="options" />
+  </div>
+</template>
+
+<style>
+html,
+body,
+#app {
+  height: 100%;
+  width: 100%;
+}
+
+body {
+  margin: 0;
+  padding: 0;
+}
+</style>
+```
+
+### Handle errors only with the `VuePDFjs` component
+
+In this example, we handle errors only with the `VuePDFjs` component and display a message to the user.
+
+```vue
+<script setup lang="ts">
+import { reactive, useTemplateRef } from "vue";
+import {
+  VuePDFjs,
+  usePDF,
+  type VuePDFjsProps,
+} from "@tuttarealstep/vue-pdf.js";
+import "@tuttarealstep/vue-pdf.js/dist/style.css";
+import enUS_FTL from "@tuttarealstep/vue-pdf.js/l10n/en-US/viewer.ftl?raw";
+
+const vuepdfjs = useTemplateRef<typeof VuePDFjs>("vuepdfjs");
+
+const options = reactive<NonNullable<VuePDFjsProps["options"]>>({
+  locale: {
+    code: "en-US",
+    ftl: enUS_FTL,
+  },
+});
+
+// Custom error handler
+const onPdfAppError = (error: unknown) => {
+  console.error(error);
+
+  alert("An error occurred while loading the PDF document.");
+};
+
+// Source options
+const sourceOptions = reactive<NonNullable<VuePDFjsProps["sourceOptions"]>>({
+  onError: onPdfAppError,
+});
+
+const source = "invalid-source";
+</script>
+
+<template>
+  <div id="app">
+    <VuePDFjs
+      ref="vuepdfjs"
+      :source="source"
+      :options="options"
+      :source-options="sourceOptions"
+    />
+  </div>
+</template>
+
+<style>
+html,
+body,
+#app {
+  height: 100%;
+  width: 100%;
+}
+
+body {
+  margin: 0;
+  padding: 0;
+}
+</style>
+```
+
+### Handle pdf.js document errors
+
+If we try to load a PDF document with the pdf.js interface it dispach a "documenterror" event. We can listen to this event and handle the error.
+
+```vue
+<script setup lang="ts">
+import { reactive, useTemplateRef } from "vue";
+import {
+  VuePDFjs,
+  usePDF,
+  type VuePDFjsProps,
+} from "@tuttarealstep/vue-pdf.js";
+import "@tuttarealstep/vue-pdf.js/dist/style.css";
+import enUS_FTL from "@tuttarealstep/vue-pdf.js/l10n/en-US/viewer.ftl?raw";
+
+const vuepdfjs = useTemplateRef<typeof VuePDFjs>("vuepdfjs");
+
+const options = reactive<NonNullable<VuePDFjsProps["options"]>>({
+  locale: {
+    code: "en-US",
+    ftl: enUS_FTL,
+  },
+});
+
+// Custom error handler
+const onPdfAppError = (error: unknown) => {
+  console.error(error);
+
+  alert("An error occurred with the PDF document.");
+};
+
+const onPdfAppLoaded = () => {
+  console.log("pdf-app:loaded");
+  console.log(vuepdfjs.value?.pdfApp);
+
+  if (!vuepdfjs.value?.pdfApp) {
+    return;
+  }
+
+  // Wait for the pages to be loaded
+  vuepdfjs.value.pdfApp.eventBus.on("pagesloaded", (e: any) => {
+    // Search for a specific text in the document
+    vuepdfjs.value?.pdfApp.eventBus.dispatch("find", {
+      query: [
+        "Dynamic languages such as JavaScript are more difficult to compile than statically typed ones.",
+      ],
+      caseSensitive: false,
+      entireWord: false,
+      highlightAll: true,
+    });
+  });
+
+  // Listen to the documenterror event
+  vuepdfjs.value.pdfApp.eventBus.on("documenterror", onPdfAppError);
+};
+
+const {
+  pdf: document,
+  info,
+  pages,
+} = usePDF("compressed.tracemonkey-pldi-09.pdf");
+
+console.log(document, info, pages);
+</script>
+
+<template>
+  <div id="app">
+    <VuePDFjs
+      ref="vuepdfjs"
+      :source="document"
+      :options="options"
+      @pdf-app:loaded="onPdfAppLoaded"
+    />
+  </div>
+</template>
+
+<style>
+html,
+body,
+#app {
+  height: 100%;
+  width: 100%;
+}
+
+body {
+  margin: 0;
+  padding: 0;
+}
+</style>
+```
diff --git a/docs/index.md b/docs/index.md
index fc5830e..b3204d4 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -31,22 +31,22 @@ features:
 
 <style>
 :root {
-  --vp-home-hero-name-color: transparent;
-  --vp-home-hero-name-background: -webkit-linear-gradient(45deg, #3fb984 45%, #31475e);
+  --vp-home-hero-name-color: transparent !important;
+  --vp-home-hero-name-background: -webkit-linear-gradient(45deg, #3fb984 45%, #31475e) !important;
 
-  --vp-home-hero-image-background-image: linear-gradient(45deg, #3fb984 50%, #31475e 50%);
-  --vp-home-hero-image-filter: blur(44px);
+  --vp-home-hero-image-background-image: linear-gradient(45deg, #3fb984 50%, #31475e 50%) !important;
+  --vp-home-hero-image-filter: blur(44px) !important;
 }
 
 @media (min-width: 640px) {
   :root {
-    --vp-home-hero-image-filter: blur(56px);
+    --vp-home-hero-image-filter: blur(56px) !important;
   }
 }
 
 @media (min-width: 960px) {
   :root {
-    --vp-home-hero-image-filter: blur(68px);
+    --vp-home-hero-image-filter: blur(68px) !important;
   }
 }
 </style>
diff --git a/packages/vue-pdfjs-playground/src/App.vue b/packages/vue-pdfjs-playground/src/App.vue
index 1889b6b..4b31d5b 100644
--- a/packages/vue-pdfjs-playground/src/App.vue
+++ b/packages/vue-pdfjs-playground/src/App.vue
@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { h, reactive, ref, useTemplateRef, watchEffect } from 'vue'
+import { h, reactive, ref, useTemplateRef, watch, watchEffect } from 'vue'
 import { VuePDFjs, usePDF } from '@tuttarealstep/vue-pdf.js'
 import '@tuttarealstep/vue-pdf.js/dist/style.css'
 import enUS_FTL from '@tuttarealstep/vue-pdf.js/l10n/en-US/viewer.ftl?raw'
@@ -7,7 +7,7 @@ import type { VuePDFjsProps } from '../../vue/dist/src/components/VuePDFjs.vue';
 
 const pdf = new URL('./assets/compressed.tracemonkey-pldi-09.pdf', import.meta.url);
 
-const vuepdfjs = useTemplateRef<typeof VuePDFjs>('vuepdfjs')
+const vuepdfjs = useTemplateRef<InstanceType<typeof VuePDFjs>>('vuepdfjs')
 
 const hideToolbar = ref(false)
 const hideSidebar = ref(false)
@@ -21,8 +21,8 @@ const options = reactive<NonNullable<VuePDFjsProps['options']>>({
     visible: true,
     options: {
       sidebarToggle: false,
-      secondaryOpenFile: false,
-      /*secondaryDownload: false,
+      /*secondaryOpenFile: false,
+      secondaryDownload: false,
       secondaryPrint: false,
       scaleSelect: false,
       print: false,
@@ -31,9 +31,10 @@ const options = reactive<NonNullable<VuePDFjsProps['options']>>({
   },
   sidebar: {
     visible: true
-  }
+  },
 })
 
+
 watchEffect(() => {
   if (options.toolbar) {
     options.toolbar.visible = !hideToolbar.value
@@ -48,6 +49,12 @@ watchEffect(() => {
     options.sidebar.visible = !hideSidebar.value
 })
 
+const onPdfAppError = (error: unknown) => {
+  console.error(error)
+
+  alert("An error occurred whit the PDF document.")
+}
+
 const onPdfAppLoaded = () => {
   console.log('pdf-app:loaded')
   console.log(vuepdfjs.value?.pdfApp)
@@ -64,20 +71,40 @@ const onPdfAppLoaded = () => {
       highlightAll: true
     })
   })
+
+  vuepdfjs.value.pdfApp.eventBus.on('documenterror', onPdfAppError)
 }
 
-const { pdf: document, info, pages } = usePDF(pdf)
+const sourceOptions = reactive<NonNullable<VuePDFjsProps['sourceOptions']>>({
+  onError: onPdfAppError
+})
+
+const { pdf: document, info, pages } = usePDF(pdf, {
+  onError: onPdfAppError
+})
+
+const source = ref<any>()
+
+watch(document, (value) => {
+  source.value = value
+})
 
-console.log(document, info, pages)
 </script>
 
 <template>
   <div>
     <input type="checkbox" v-model="hideToolbar" /> Hide Toolbar
     <input type="checkbox" v-model="hideSidebar" /> Hide Sidebar
+    <button type="button" class="custom-button" @click="source = `invalid${new Date().getTime()}.pdf`">
+      Load Invalid PDF
+    </button>
+  </div>
+  <div>
+    We have {{ vuepdfjs?.pdfPages }} pages in this document.
   </div>
   <div id="playground">
-    <VuePDFjs ref="vuepdfjs" :source="document" :options="options" @pdf-app:loaded="onPdfAppLoaded" />
+    <VuePDFjs ref="vuepdfjs" :source :options="options" :source-options="sourceOptions"
+      @pdf-app:loaded="onPdfAppLoaded" />
   </div>
 </template>
 
diff --git a/packages/vue/README.md b/packages/vue/README.md
index 242ae49..fb078c1 100644
--- a/packages/vue/README.md
+++ b/packages/vue/README.md
@@ -1,4 +1,4 @@
-# vue-pdfjs
+# vue-pdfjs [![NPM Version](https://img.shields.io/npm/v/%40tuttarealstep%2Fvue-pdf.js)](https://www.npmjs.com/package/@tuttarealstep/vue-pdf.js)
 
 `vue-pdfjs` is a Vue 3 component for displaying PDF files using the standard `pdf.js` viewer. This package provides a simple and powerful integration to embed PDF viewers in Vue applications.
 
@@ -23,44 +23,51 @@ npm install @tuttarealstep/vue-pdf.js
 
 ```vue
 <script setup lang="ts">
-import { reactive, useTemplateRef } from 'vue'
-import { VuePDFjs } from '@tuttarealstep/vue-pdf.js'
-import '@tuttarealstep/vue-pdf.js/dist/style.css'
-import enUS_FTL from '@tuttarealstep/vue-pdf.js/l10n/en-US/viewer.ftl?raw'
+import { reactive, useTemplateRef } from "vue";
+import { VuePDFjs } from "@tuttarealstep/vue-pdf.js";
+import "@tuttarealstep/vue-pdf.js/dist/style.css";
+import enUS_FTL from "@tuttarealstep/vue-pdf.js/l10n/en-US/viewer.ftl?raw";
 
-const pdf = new URL('./path/to/custom.pdf', import.meta.url).href
+const pdf = new URL("./path/to/custom.pdf", import.meta.url).href;
 
-const vuepdfjs = useTemplateRef<typeof VuePDFjs>('vuepdfjs')
+const vuepdfjs = useTemplateRef<typeof VuePDFjs>("vuepdfjs");
 
 const options = reactive({
   locale: {
-    code: 'en-US',
-    ftl: enUS_FTL
-  }
-})
+    code: "en-US",
+    ftl: enUS_FTL,
+  },
+});
 
 const onPdfAppLoaded = () => {
-  console.log('pdf-app:loaded')
-  console.log(vuepdfjs.value?.pdfApp)
+  console.log("pdf-app:loaded");
+  console.log(vuepdfjs.value?.pdfApp);
 
   if (!vuepdfjs.value?.pdfApp) {
-    return
+    return;
   }
 
-  vuepdfjs.value.pdfApp.eventBus.on('pagesloaded', (e: any) => {
-    vuepdfjs.value?.pdfApp.eventBus.dispatch('find', {
-      query: ['Dynamic languages such as JavaScript are more difficult to compile than statically typed ones.'],
+  vuepdfjs.value.pdfApp.eventBus.on("pagesloaded", (e: any) => {
+    vuepdfjs.value?.pdfApp.eventBus.dispatch("find", {
+      query: [
+        "Dynamic languages such as JavaScript are more difficult to compile than statically typed ones.",
+      ],
       caseSensitive: false,
       entireWord: false,
-      highlightAll: true
-    })
-  })
-}
+      highlightAll: true,
+    });
+  });
+};
 </script>
 
 <template>
   <div id="app">
-    <VuePDFjs ref="vuepdfjs" :source="pdf" :options="options" @pdf-app:loaded="onPdfAppLoaded" />
+    <VuePDFjs
+      ref="vuepdfjs"
+      :source="pdf"
+      :options="options"
+      @pdf-app:loaded="onPdfAppLoaded"
+    />
   </div>
 </template>
 
@@ -83,46 +90,55 @@ body {
 
 ```vue
 <script setup lang="ts">
-import { reactive, useTemplateRef } from 'vue'
-import { VuePDFjs, usePDF } from '@tuttarealstep/vue-pdf.js'
-import '@tuttarealstep/vue-pdf.js/dist/style.css'
-import enUS_FTL from '@tuttarealstep/vue-pdf.js/l10n/en-US/viewer.ftl?raw'
+import { reactive, useTemplateRef } from "vue";
+import { VuePDFjs, usePDF } from "@tuttarealstep/vue-pdf.js";
+import "@tuttarealstep/vue-pdf.js/dist/style.css";
+import enUS_FTL from "@tuttarealstep/vue-pdf.js/l10n/en-US/viewer.ftl?raw";
 
-const vuepdfjs = useTemplateRef<typeof VuePDFjs>('vuepdfjs')
+const vuepdfjs = useTemplateRef<typeof VuePDFjs>("vuepdfjs");
 
 const options = reactive({
   locale: {
-    code: 'en-US',
-    ftl: enUS_FTL
-  }
-})
+    code: "en-US",
+    ftl: enUS_FTL,
+  },
+});
 
 const onPdfAppLoaded = () => {
-  console.log('pdf-app:loaded')
-  console.log(vuepdfjs.value?.pdfApp)
+  console.log("pdf-app:loaded");
+  console.log(vuepdfjs.value?.pdfApp);
 
   if (!vuepdfjs.value?.pdfApp) {
-    return
+    return;
   }
 
-  vuepdfjs.value.pdfApp.eventBus.on('pagesloaded', (e: any) => {
-    vuepdfjs.value?.pdfApp.eventBus.dispatch('find', {
-      query: ['Dynamic languages such as JavaScript are more difficult to compile than statically typed ones.'],
+  vuepdfjs.value.pdfApp.eventBus.on("pagesloaded", (e: any) => {
+    vuepdfjs.value?.pdfApp.eventBus.dispatch("find", {
+      query: [
+        "Dynamic languages such as JavaScript are more difficult to compile than statically typed ones.",
+      ],
       caseSensitive: false,
       entireWord: false,
-      highlightAll: true
-    })
-  })
-}
+      highlightAll: true,
+    });
+  });
+};
 
-const { pdf, info, pages} = usePDF(new URL('./path/to/custom.pdf', import.meta.url).href) // or any other source type
+const { pdf, info, pages } = usePDF(
+  new URL("./path/to/custom.pdf", import.meta.url).href
+); // or any other source type
 
-console.log(document, info, pages)
+console.log(document, info, pages);
 </script>
 
 <template>
   <div id="app">
-    <VuePDFjs ref="vuepdfjs" :source="pdf" :options="options" @pdf-app:loaded="onPdfAppLoaded" />
+    <VuePDFjs
+      ref="vuepdfjs"
+      :source="pdf"
+      :options="options"
+      @pdf-app:loaded="onPdfAppLoaded"
+    />
   </div>
 </template>
 
diff --git a/packages/vue/package.json b/packages/vue/package.json
index 74a0680..af159df 100644
--- a/packages/vue/package.json
+++ b/packages/vue/package.json
@@ -1,7 +1,7 @@
 {
   "name": "@tuttarealstep/vue-pdf.js",
   "description": "A Vue component for displaying PDF files using the standard `pdf.js` viewer. This package provides a simple and powerful integration to embed PDF viewers in Vue applications.",
-  "version": "1.0.8",
+  "version": "1.0.9",
   "private": false,
   "type": "module",
   "author": "Stefano Valenzano (https://github.com/tuttarealstep)",
diff --git a/packages/vue/src/components/VuePDFjs.vue b/packages/vue/src/components/VuePDFjs.vue
index 68ca19e..11b06ff 100644
--- a/packages/vue/src/components/VuePDFjs.vue
+++ b/packages/vue/src/components/VuePDFjs.vue
@@ -1,7 +1,7 @@
 <script setup lang="ts">
 import '../assets/scss/main.scss'
 
-import { onMounted, onUnmounted, ref, shallowRef, watch, provide } from 'vue';
+import { onMounted, onUnmounted, ref, shallowRef, provide, watch } from 'vue';
 import { initViewer, PDFViewerApplicationOptions } from '../scripts/viewer';
 
 import OuterContainer from './OuterContainer.vue';
@@ -49,6 +49,24 @@ const pdfInfo = shallowRef<PDFInfo | {}>({})
 provide(toolbarOptionsKey, props.options?.toolbar)
 provide(sidebarOptionsKey, props.options?.sidebar)
 
+async function loadDocumentInfo(document: PDFDocumentProxy) {
+  pdfDocument.value = document;
+  pdfLoadingTask.value = document.loadingTask;
+  pdfPages.value = document.numPages;
+
+  const metadata = await document.getMetadata()
+  const attachments = (await document.getAttachments()) as Record<string, unknown>
+  const javascript = await document.getJSActions()
+  const outline = await document.getOutline()
+
+  pdfInfo.value = {
+    metadata,
+    attachments,
+    javascript,
+    outline,
+  }
+}
+
 async function init() {
   if (!container.value) {
     throw new Error('Container not found');
@@ -75,9 +93,21 @@ async function init() {
     pdfApp.value = await initViewer(container.value);
     await pdfApp.value.initializedPromise;
 
+    pdfApp.value.eventBus.on('documentloaded', (
+      event: {
+        source: PDFViewerApplication
+      }
+    ) => {
+      loadDocumentInfo(event.source.pdfDocument);
+    })
+
     emit('pdf-app:loaded');
-  } catch (error) {
+  } catch (error: unknown) {
     console.error(error);
+
+    if (props.sourceOptions?.onError) {
+      props.sourceOptions.onError(error);
+    }
   }
 
   isLoading.value = false;
@@ -90,6 +120,7 @@ function clearCacheTimeout() {
     clearTimeout(cacheTimeoutId)
 }
 
+
 async function initDocument(document: PDFDocumentProxy | null) {
   if (!document || !pdfApp.value) {
     return;
@@ -97,35 +128,35 @@ async function initDocument(document: PDFDocumentProxy | null) {
 
   clearCacheTimeout()
 
-  pdfDocument.value = document;
-  pdfLoadingTask.value = document.loadingTask;
-  pdfPages.value = document.numPages;
-
-  const metadata = await document.getMetadata()
-  const attachments = (await document.getAttachments()) as Record<string, unknown>
-  const javascript = await document.getJSActions()
-  const outline = await document.getOutline()
-
-  pdfInfo.value = {
-    metadata,
-    attachments,
-    javascript,
-    outline,
-  }
+  await loadDocumentInfo(document);
 
   pdfApp.value?.load(document);
 }
 
 async function openSource(source: PDFSource | PDFSourceWithOptions | PDFDocumentProxy) {
-  if (source !== undefined && source !== null) {
-    if (source instanceof PDFDocumentProxy) {
-      initDocument(source);
-    } else {
-      initDocument(await processSource(source, props.sourceOptions));
+  try {
+    if (source !== undefined && source !== null) {
+      if (source instanceof PDFDocumentProxy) {
+        initDocument(source);
+      } else {
+        initDocument(await processSource(source, {
+          ...props.sourceOptions,
+          onError: (error) => {
+            if (props.sourceOptions?.onError) {
+              props.sourceOptions.onError(error);
+            } else {
+              console.error(error);
+            }
+          }
+        }));
+      }
     }
-  } else {
-    if (pdfApp.value) {
-      await pdfApp.value.close();
+  }
+  catch (error) {
+    if (props.sourceOptions?.onError) {
+      props.sourceOptions.onError(error);
+    } else {
+      console.error(error);
     }
   }
 }
@@ -141,8 +172,9 @@ onMounted(async () => {
     await openSource(props.source);
 })
 
-onUnmounted(() => {
-  pdfDocument.value?.destroy();
+onUnmounted(async () => {
+  if (pdfApp.value)
+    await pdfApp.value.close();
 })
 
 defineExpose({
diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts
index dc40e23..ff073fa 100644
--- a/packages/vue/src/index.ts
+++ b/packages/vue/src/index.ts
@@ -1,12 +1,14 @@
-import type { Plugin } from 'vue';
-import VuePDFjs from './components/VuePDFjs.vue';
-import usePDF from './composable/usePDF';
+import type { Plugin } from 'vue'
+import VuePDFjs, { type VuePDFjsProps } from './components/VuePDFjs.vue'
+import usePDF from './composable/usePDF'
+export * from './types'
 
 export const VuePDFjsPlugin: Plugin = {
-    install(app) {
-        app.component('VuePDFjs', VuePDFjs);
-    }
-};
+  install(app) {
+    app.component('VuePDFjs', VuePDFjs)
+  }
+}
 
-export { VuePDFjs, usePDF };
-export default VuePDFjsPlugin;
\ No newline at end of file
+export type { VuePDFjsProps }
+export { VuePDFjs, usePDF }
+export default VuePDFjsPlugin
diff --git a/packages/vue/src/scripts/utils.ts b/packages/vue/src/scripts/utils.ts
index 3bb95be..f42b736 100644
--- a/packages/vue/src/scripts/utils.ts
+++ b/packages/vue/src/scripts/utils.ts
@@ -8,7 +8,12 @@ import {
   UpdatePasswordFn
 } from '../types'
 
-const getByteArray = function (file: File) {
+/**
+ * Converts a File object to a Uint8Array.
+ * @param {File} file - The file to convert.
+ * @returns {Promise<Uint8Array>} A promise that resolves to a Uint8Array.
+ */
+const getByteArray = function (file: File): Promise<Uint8Array> {
   const fileReader = new FileReader()
   return new Promise(function (resolve: (value: Uint8Array) => void, reject) {
     fileReader.readAsArrayBuffer(file)
@@ -24,6 +29,12 @@ const getByteArray = function (file: File) {
   })
 }
 
+/**
+ * Parses a PDF source file and returns a PDFDocumentLoadingTask.
+ * @param {PDFSource} source - The source of the PDF file.
+ * @returns {Promise<PDFDocumentLoadingTask>} A promise that resolves to a PDFDocumentLoadingTask.
+ * @throws {Error} If the source is invalid.
+ */
 export async function parseSourceFile(source: PDFSource): Promise<PDFDocumentLoadingTask> {
   if (source == null || source === undefined) {
     throw new Error('Invalid parameter')
@@ -66,6 +77,12 @@ export async function parseSourceFile(source: PDFSource): Promise<PDFDocumentLoa
   return getDocument(source!)
 }
 
+/**
+ * Processes a PDF source and returns a PDFDocumentProxy.
+ * @param {PDFSource | PDFSourceWithOptions} inputSource - The input source of the PDF file.
+ * @param {PDFSourceOptions} [sourceOptions] - Optional source options.
+ * @returns {Promise<PDFDocumentProxy | null>} A promise that resolves to a PDFDocumentProxy or null.
+ */
 export async function processSource(
   inputSource: PDFSource | PDFSourceWithOptions,
   sourceOptions?: PDFSourceOptions
@@ -94,9 +111,15 @@ export async function processSource(
   const loadingTask = await parseSourceFile(source).catch((error) => {
     if (options?.onError) {
       options.onError(error)
+    } else {
+      console.error(error)
     }
   })
 
+  if (!loadingTask) {
+    return null
+  }
+
   if (options?.onProgress) {
     loadingTask.onProgress = options.onProgress
   }
@@ -110,5 +133,11 @@ export async function processSource(
     loadingTask.onPassword = onPassword
   }
 
-  return loadingTask.promise
+  return loadingTask.promise.catch((error: any) => {
+    if (options?.onError) {
+      options.onError(error)
+    } else {
+      console.error(error)
+    }
+  })
 }
diff --git a/packages/vue/src/types.ts b/packages/vue/src/types.ts
index 711ff70..1d5a450 100644
--- a/packages/vue/src/types.ts
+++ b/packages/vue/src/types.ts
@@ -1,8 +1,13 @@
+import {
+  TypedArray,
+  DocumentInitParameters,
+  PDFDocumentLoadingTask,
+  OnProgressParameters
+  //@ts-ignore
+} from 'pdf.js/src/display/api'
+
 //@ts-ignore
-import { TypedArray, DocumentInitParameters, PDFDocumentLoadingTask, OnProgressParameters, PDFDocumentProxy } from "pdf.js/src/display/api";
-//@ts-ignore
-import { Metadata } from "pdf.js/src/display/metadata";
-import { ToolbarContainerProps } from "./components/ToolbarContainer.vue";
+import { Metadata } from 'pdf.js/src/display/metadata'
 
 export type OnProgressCallback = (progressData: OnProgressParameters) => void
 export type UpdatePasswordFn = (newPassword: string) => void
@@ -10,37 +15,37 @@ export type OnPasswordCallback = (updatePassword: UpdatePasswordFn, reason: any)
 export type OnErrorCallback = (error: any) => void
 
 export interface PDFSourceOptions {
-    onProgress?: OnProgressCallback
-    onPassword?: OnPasswordCallback
-    onError?: OnErrorCallback
-    password?: string
+  onProgress?: OnProgressCallback
+  onPassword?: OnPasswordCallback
+  onError?: OnErrorCallback
+  password?: string
 }
 
 export type PDFSource =
-    | string
-    | URL
-    | TypedArray
-    | ArrayBuffer
-    | DocumentInitParameters //End of standard types
-    | File
-    | Blob
-    | PDFDocumentLoadingTask
-    | undefined
-    | null
+  | string
+  | URL
+  | TypedArray
+  | ArrayBuffer
+  | DocumentInitParameters //End of standard types
+  | File
+  | Blob
+  | PDFDocumentLoadingTask
+  | undefined
+  | null
 
 export interface PDFSourceWithOptions {
-    source?: PDFSource
-    options?: PDFSourceOptions
+  source?: PDFSource
+  options?: PDFSourceOptions
 }
 
 export interface PDFInfoMetadata {
-    info: any
-    metadata: Metadata
+  info: any
+  metadata: Metadata
 }
 
 export interface PDFInfo {
-    metadata: PDFInfoMetadata
-    attachments: Record<string, unknown>
-    javascript: string[] | null
-    outline: any
-}
\ No newline at end of file
+  metadata: PDFInfoMetadata
+  attachments: Record<string, unknown>
+  javascript: string[] | null
+  outline: any
+}
diff --git a/packages/vue/tests/mount.test.ts b/packages/vue/tests/mount.test.ts
index a2602a2..bfd0c39 100644
--- a/packages/vue/tests/mount.test.ts
+++ b/packages/vue/tests/mount.test.ts
@@ -9,9 +9,7 @@ const {
   pdf: pdfDocument,
   info: pdfInfo,
   pages: pdfPages
-} = usePDF(
-  'https://raw.githubusercontent.com/mozilla/pdf.js/v4.10.38/web/compressed.tracemonkey-pldi-09.pdf'
-)
+} = usePDF(new URL('../../vue-pdfjs-playground/src/assets/compressed.tracemonkey-pldi-09.pdf', import.meta.url))
 
 beforeAll(async () => {
   await vi.waitUntil(() => pdfDocument.value, { timeout: 5000 })
@@ -32,8 +30,7 @@ test('Render component', () => {
           ftl: enUS_FTL
         }
       }
-    },
-    
+    }
   })
 
   //Expect the pdfInfo.value.metadata to be an object
diff --git a/packages/vue/tests/options.test.ts b/packages/vue/tests/options.test.ts
index 2ae4277..78533e2 100644
--- a/packages/vue/tests/options.test.ts
+++ b/packages/vue/tests/options.test.ts
@@ -1,17 +1,19 @@
 import { beforeAll, expect, test, vi } from 'vitest'
 import { render } from 'vitest-browser-vue'
-import { VuePDFjs, usePDF } from '@tuttarealstep/vue-pdf.js'
+import { VuePDFjs, usePDF, type VuePDFjsProps } from '@tuttarealstep/vue-pdf.js'
 import '@tuttarealstep/vue-pdf.js/dist/style.css'
 // @ts-ignore
 import enUS_FTL from '../../../node_modules/pdf.js/l10n/en-US/viewer.ftl?raw'
-import { ref } from 'vue'
 
 const {
   pdf: pdfDocument,
-  info: pdfInfo,
+  //info: pdfInfo,
   pages: pdfPages
 } = usePDF(
-  'https://raw.githubusercontent.com/mozilla/pdf.js/v4.10.38/web/compressed.tracemonkey-pldi-09.pdf'
+  new URL(
+    '../../vue-pdfjs-playground/src/assets/compressed.tracemonkey-pldi-09.pdf',
+    import.meta.url
+  )
 )
 
 beforeAll(async () => {
@@ -55,3 +57,56 @@ test('Test enable/disable options', () => {
   // Expect the "#editorStamp" display to not be "none"
   expect(document.getElementById('editorStamp')?.style.display).toBe('none')
 })
+
+test('Catch error when source is invalid - usePDF composable', async () => {
+  const invalidSource = 'invalid-source'
+
+  const onErrorCallback = vi.fn((error: unknown) => {
+    console.error(error)
+  })
+
+  const { pdf, info, pages } = usePDF(invalidSource, {
+    onError: onErrorCallback
+  })
+
+  expect(pdf.value).toBe(undefined)
+  expect(info.value).toBe(undefined)
+  expect(pages.value).toBe(0)
+
+  await new Promise((resolve) => setTimeout(resolve, 100))
+  await vi.waitFor(() => onErrorCallback.mock.calls.length > 0)
+
+  expect(onErrorCallback).toHaveBeenCalled()
+})
+
+test('Catch error when source is invalid - without composable', async () => {
+  //Add a default css to make body > div height 100vh
+  const style = document.createElement('style')
+  style.innerHTML = 'body > div { height: 100vh }'
+  document.head.appendChild(style)
+
+  const onErrorCallback = vi.fn((error: unknown) => {
+    console.error(error)
+  })
+
+  const props = {
+    source: 'invalid-source',
+    options: {
+      locale: {
+        code: 'en-US',
+        ftl: enUS_FTL
+      }
+    },
+    sourceOptions: {
+      onError: onErrorCallback
+    }
+  } as VuePDFjsProps
+
+  render(VuePDFjs, {
+    props
+  })
+
+  await new Promise((resolve) => setTimeout(resolve, 100))
+
+  expect(onErrorCallback).toHaveBeenCalled()
+})
diff --git a/packages/vue/tests/utils.test.ts b/packages/vue/tests/utils.test.ts
new file mode 100644
index 0000000..a3e33d4
--- /dev/null
+++ b/packages/vue/tests/utils.test.ts
@@ -0,0 +1,48 @@
+import { parseSourceFile, processSource } from '../src/scripts/utils'
+import { describe, it, expect, vi } from 'vitest'
+//@ts-expect-error
+import { PDFDocumentLoadingTask, PDFDocumentProxy } from 'pdf.js/src/display/api'
+import { PDFJSWorker } from '../src/scripts/viewer'
+
+//@ts-expect-error
+import * as PDFJS from 'pdf.js/src/pdf.js'
+
+PDFJS.GlobalWorkerOptions.workerPort = new PDFJSWorker()
+
+describe('utils.ts', () => {
+  describe('parseSourceFile', () => {
+    it('should throw an error for null source', async () => {
+      await expect(parseSourceFile(null)).rejects.toThrow('Invalid parameter')
+    })
+
+    it('should return the same PDFDocumentLoadingTask if source is a File', async () => {
+      const file = new File([''], 'test.pdf')
+      const result = await parseSourceFile(file)
+      expect(result).toBeInstanceOf(PDFDocumentLoadingTask)
+    })
+
+    it('should return the same PDFDocumentLoadingTask if source is a Blob', async () => {
+      const blob = new Blob([''], { type: 'application/pdf' })
+      const result = await parseSourceFile(blob)
+      expect(result).toBeInstanceOf(PDFDocumentLoadingTask)
+    })
+
+    it('should return the same PDFDocumentLoadingTask if source is a string', async () => {
+      const result = await parseSourceFile('https://example.com/test.pdf')
+      expect(result).toBeInstanceOf(PDFDocumentLoadingTask)
+    })
+
+    it('should return the same PDFDocumentLoadingTask if source is already a PDFDocumentLoadingTask', async () => {
+      const loadingTask = new PDFDocumentLoadingTask()
+      const result = await parseSourceFile(loadingTask)
+      expect(result).toBe(loadingTask)
+    })
+  })
+
+  describe('processSource', () => {
+    it('should return null for null inputSource', async () => {
+      const result = await processSource(null)
+      expect(result).toBeNull()
+    })
+  })
+})