unpdf 1.4.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +32 -38
  2. package/dist/index.cjs +60 -2
  3. package/dist/index.d.cts +5 -1
  4. package/dist/index.d.mts +5 -1
  5. package/dist/index.d.ts +5 -1
  6. package/dist/index.mjs +60 -2
  7. package/dist/pdfjs.mjs +107 -1
  8. package/dist/types/src/display/annotation_layer.d.ts +14 -11
  9. package/dist/types/src/display/annotation_storage.d.ts +8 -8
  10. package/dist/types/src/display/api.d.ts +81 -57
  11. package/dist/types/src/display/api_utils.d.ts +1 -1
  12. package/dist/types/src/display/binary_data_factory.d.ts +26 -0
  13. package/dist/types/src/display/canvas.d.ts +19 -16
  14. package/dist/types/src/display/canvas_dependency_tracker.d.ts +66 -10
  15. package/dist/types/src/display/canvas_factory.d.ts +3 -1
  16. package/dist/types/src/display/display_utils.d.ts +4 -3
  17. package/dist/types/src/display/draw_layer.d.ts +2 -6
  18. package/dist/types/src/display/editor/alt_text.d.ts +1 -1
  19. package/dist/types/src/display/editor/annotation_editor_layer.d.ts +7 -12
  20. package/dist/types/src/display/editor/color_picker.d.ts +2 -2
  21. package/dist/types/src/display/editor/comment.d.ts +12 -0
  22. package/dist/types/src/display/editor/draw.d.ts +3 -7
  23. package/dist/types/src/display/editor/drawers/freedraw.d.ts +3 -3
  24. package/dist/types/src/display/editor/drawers/signaturedraw.d.ts +13 -13
  25. package/dist/types/src/display/editor/editor.d.ts +16 -13
  26. package/dist/types/src/display/editor/freetext.d.ts +2 -2
  27. package/dist/types/src/display/editor/highlight.d.ts +3 -3
  28. package/dist/types/src/display/editor/toolbar.d.ts +2 -2
  29. package/dist/types/src/display/editor/tools.d.ts +37 -1
  30. package/dist/types/src/display/fetch_stream.d.ts +3 -56
  31. package/dist/types/src/display/font_loader.d.ts +2 -1
  32. package/dist/types/src/display/network.d.ts +14 -80
  33. package/dist/types/src/display/network_stream.d.ts +2 -0
  34. package/dist/types/src/display/network_utils.d.ts +3 -3
  35. package/dist/types/src/display/node_stream.d.ts +2 -52
  36. package/dist/types/src/display/node_utils.d.ts +5 -19
  37. package/dist/types/src/{shared/obj-bin-transform.d.ts → display/obj_bin_transform_display.d.ts} +21 -18
  38. package/dist/types/src/display/pages_mapper.d.ts +72 -0
  39. package/dist/types/src/display/pattern_helper.d.ts +7 -2
  40. package/dist/types/src/display/text_layer.d.ts +16 -10
  41. package/dist/types/src/display/text_layer_images.d.ts +10 -0
  42. package/dist/types/src/display/transport_stream.d.ts +8 -64
  43. package/dist/types/src/display/webgpu_mesh.d.ts +3 -0
  44. package/dist/types/src/display/worker_options.d.ts +2 -2
  45. package/dist/types/src/display/xfa_layer.d.ts +3 -3
  46. package/dist/types/src/pdf.d.ts +5 -1
  47. package/dist/types/src/{interfaces.d.ts → shared/base_pdf_stream.d.ts} +26 -27
  48. package/dist/types/src/shared/obj_bin_transform_utils.d.ts +26 -0
  49. package/dist/types/src/shared/util.d.ts +14 -5
  50. package/dist/types/web/annotation_editor_layer_builder.d.ts +7 -8
  51. package/dist/types/web/annotation_layer_builder.d.ts +9 -9
  52. package/dist/types/web/app_options.d.ts +1 -1
  53. package/dist/types/web/autolinker.d.ts +3 -2
  54. package/dist/types/web/base_download_manager.d.ts +24 -0
  55. package/dist/types/web/base_pdf_page_view.d.ts +5 -5
  56. package/dist/types/web/comment_manager.d.ts +2 -2
  57. package/dist/types/web/download_manager.d.ts +3 -12
  58. package/dist/types/web/draw_layer_builder.d.ts +0 -12
  59. package/dist/types/web/genericl10n.d.ts +6 -10
  60. package/dist/types/web/l10n.d.ts +3 -6
  61. package/dist/types/web/menu.d.ts +11 -0
  62. package/dist/types/web/pdf_find_controller.d.ts +9 -11
  63. package/dist/types/web/pdf_history.d.ts +3 -3
  64. package/dist/types/web/pdf_link_service.d.ts +2 -7
  65. package/dist/types/web/pdf_page_detail_view.d.ts +1 -7
  66. package/dist/types/web/pdf_page_view.d.ts +17 -11
  67. package/dist/types/web/pdf_rendering_queue.d.ts +10 -14
  68. package/dist/types/web/pdf_thumbnail_view.d.ts +18 -42
  69. package/dist/types/web/pdf_thumbnail_viewer.d.ts +57 -13
  70. package/dist/types/web/pdf_viewer.component.d.ts +1 -1
  71. package/dist/types/web/pdf_viewer.d.ts +30 -27
  72. package/dist/types/web/renderable_view.d.ts +33 -0
  73. package/dist/types/web/sidebar.d.ts +65 -0
  74. package/dist/types/web/struct_tree_layer_builder.d.ts +1 -1
  75. package/dist/types/web/text_accessibility.d.ts +1 -1
  76. package/dist/types/web/text_layer_builder.d.ts +11 -6
  77. package/dist/types/web/ui_utils.d.ts +3 -11
  78. package/dist/types/web/xfa_layer_builder.d.ts +4 -4
  79. package/package.json +22 -17
  80. package/dist/types/src/display/cmap_reader_factory.d.ts +0 -25
  81. package/dist/types/src/display/standard_fontdata_factory.d.ts +0 -20
  82. package/dist/types/src/display/wasm_factory.d.ts +0 -20
  83. package/dist/types/web/interfaces.d.ts +0 -174
package/README.md CHANGED
@@ -1,40 +1,25 @@
1
1
  # unpdf
2
2
 
3
- A collection of utilities for PDF extraction and rendering. Designed specifically for serverless environments, but it also works in Node.js, Deno, Bun and the browser. `unpdf` is particularly useful for serverless AI applications, especially for summarizing PDF documents in document analysis workflows.
3
+ Utilities for PDF extraction and rendering across all JavaScript runtimes – Node.js, Deno, Bun, the browser, and serverless environments like Cloudflare Workers. Especially useful for AI applications that need to summarize or analyze PDF documents.
4
4
 
5
- This library ships with a serverless build/redistribution of Mozilla's [PDF.js](https://github.com/mozilla/pdf.js) that is optimized for edge environments. Some string replacements, global mocks and inlining the PDF.js worker allow the browser code to become platform agnostic. See [`pdfjs.rollup.config.ts`](./pdfjs.rollup.config.ts) for the details.
6
-
7
- This library is also intended as a modern alternative to the unmaintained but still popular [`pdf-parse`](https://www.npmjs.com/package/pdf-parse).
5
+ Ships with a serverless build of Mozilla's [PDF.js](https://github.com/mozilla/pdf.js), optimized for edge environments. If you're coming from [`pdf-parse`](https://www.npmjs.com/package/pdf-parse), `unpdf` is a modern, actively maintained alternative with broader runtime support.
8
6
 
9
7
  ## Features
10
8
 
11
- - 🏗️ Made for Node.js, browser and serverless environments
9
+ - 🏗️ Works in Node.js, browser and serverless environments
12
10
  - 🪭 Includes serverless build of PDF.js ([`unpdf/pdfjs`](./package.json#L34))
13
11
  - 💬 Extract [text](#extract-text-from-pdf), [links](#extractlinks), and [images](#extractimages) from PDF files
14
12
  - 🧠 Perfect for AI applications and PDF summarization
15
- - 🧱 Opt-in to legacy PDF.js build
16
- - 💨 Zero dependencies
17
-
18
- ## PDF.js Compatibility
19
-
20
- > [!Tip]
21
- > The serverless PDF.js bundle provided by `unpdf` is built from PDF.js v5.4.296.
22
-
23
- You can use an [official PDF.js build](#official-or-legacy-pdfjs-build) by using the [`definePDFJSModule`](#definepdfjsmodule) method. This is useful if you want to use a specific version or a custom build of PDF.js.
13
+ - 🧱 Opt-in to official or legacy PDF.js build
24
14
 
25
15
  ## Installation
26
16
 
27
- Run the following command to add `unpdf` to your project.
28
-
29
17
  ```bash
30
18
  # pnpm
31
- pnpm add -D unpdf
19
+ pnpm add unpdf
32
20
 
33
21
  # npm
34
- npm install -D unpdf
35
-
36
- # yarn
37
- yarn add -D unpdf
22
+ npm install unpdf
38
23
  ```
39
24
 
40
25
  ## Usage
@@ -44,15 +29,11 @@ yarn add -D unpdf
44
29
  ```ts
45
30
  import { extractText, getDocumentProxy } from 'unpdf'
46
31
 
47
- // Either fetch a PDF file from the web or load it from the file system
32
+ // Fetch a PDF from the web or load it from the file system
48
33
  const buffer = await fetch('https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf')
49
34
  .then(res => res.arrayBuffer())
50
- const buffer = await readFile('./dummy.pdf')
51
35
 
52
- // Then, load the PDF file into a PDF.js document
53
36
  const pdf = await getDocumentProxy(new Uint8Array(buffer))
54
-
55
- // Finally, extract the text from the PDF file
56
37
  const { totalPages, text } = await extractText(pdf, { mergePages: true })
57
38
 
58
39
  console.log(`Total pages: ${totalPages}`)
@@ -64,9 +45,9 @@ console.log(text)
64
45
  Usually you don't need to worry about the PDF.js build. `unpdf` ships with a serverless build of the latest PDF.js version. However, if you want to use the official PDF.js version or the legacy build, you can define a custom PDF.js module.
65
46
 
66
47
  > [!WARNING]
67
- > PDF.js v5.x uses `Promise.withResolvers`, which may not be supported in all environments, such as Node < 22. Consider to use the bundled serverless build, which includes a polyfill, or use an older version of PDF.js.
48
+ > PDF.js v5.x uses `Promise.withResolvers`, which may not be supported in all environments, such as Node < 22. Consider using the bundled serverless build, which includes a polyfill, or use an older version of PDF.js.
68
49
 
69
- For example, if you want to use the official PDF.js build, you can do the following:
50
+ For example, if you want to use the official PDF.js build:
70
51
 
71
52
  ```ts
72
53
  import { definePDFJSModule, extractText, getDocumentProxy } from 'unpdf'
@@ -107,6 +88,17 @@ const document = await getDocument(new Uint8Array(data)).promise
107
88
  console.log(await document.getMetadata())
108
89
  ```
109
90
 
91
+ ## How It Works
92
+
93
+ > [!NOTE]
94
+ > The serverless PDF.js bundle is built from PDF.js v5.6.205.
95
+
96
+ Heart and soul of this package is the [`pdfjs.rollup.config.ts`](./pdfjs.rollup.config.ts) file. It uses [Rollup](https://rollupjs.org/) to bundle PDF.js into a single file for serverless environments. The key techniques:
97
+
98
+ - **String replacements** strip browser-specific references from the PDF.js source.
99
+ - **Worker inlining** embeds the PDF.js worker directly into the main bundle, since serverless runtimes can't load separate worker files.
100
+ - **Global polyfills** provide missing APIs like `FinalizationRegistry` (unavailable in Cloudflare Workers).
101
+
110
102
  ## API
111
103
 
112
104
  ### `definePDFJSModule`
@@ -209,15 +201,7 @@ for (const link of links) console.log(link)
209
201
 
210
202
  ### `extractImages`
211
203
 
212
- Extracts images from a specific page of a PDF document, including necessary metadata such as width, height, and calculated color channels.
213
-
214
- > [!NOTE]
215
- > This method will only work in Node.js and browser environments.
216
-
217
- In order to use this method, make sure to meet the following requirements:
218
-
219
- - Use the official PDF.js build (see below for details).
220
- - Install the [`@napi-rs/canvas`](https://github.com/Brooooooklyn/canvas) package if you are using Node.js. This package is required to render the PDF page as an image.
204
+ Extracts images from a specific page of a PDF document, including necessary metadata such as width, height, and calculated color channels. Works with both the serverless and official PDF.js build.
221
205
 
222
206
  **Type Declaration**
223
207
 
@@ -285,9 +269,19 @@ To render a PDF page as an image, you can use the `renderPageAsImage` method. Th
285
269
 
286
270
  In order to use this method, make sure to meet the following requirements:
287
271
 
288
- - Use the official PDF.js build (see below for details).
272
+ - Use the official PDF.js build (see [Official or Legacy PDF.js Build](#official-or-legacy-pdfjs-build)).
289
273
  - Install the [`@napi-rs/canvas`](https://github.com/Brooooooklyn/canvas) package if you are using Node.js. This package is required to render the PDF page as an image.
290
274
 
275
+ > [!TIP]
276
+ > In Node.js, `getDocumentProxy` automatically sets `disableFontFace: true` and resolves `standardFontDataUrl` from your local `pdfjs-dist` package for correct font rendering. To customize this behavior, pass your own options:
277
+ >
278
+ > ```ts
279
+ > const pdf = await getDocumentProxy(buffer, {
280
+ > disableFontFace: false,
281
+ > standardFontDataUrl: 'https://unpkg.com/pdfjs-dist@latest/standard_fonts/',
282
+ > })
283
+ > ```
284
+
291
285
  **Type Declaration**
292
286
 
293
287
  ```ts
package/dist/index.cjs CHANGED
@@ -1,15 +1,73 @@
1
1
  'use strict';
2
2
 
3
+ function polyfillDOMMatrix() {
4
+ if (typeof globalThis.DOMMatrix !== "undefined")
5
+ return;
6
+ globalThis.DOMMatrix = class DOMMatrix {
7
+ a;
8
+ b;
9
+ c;
10
+ d;
11
+ e;
12
+ f;
13
+ constructor(init) {
14
+ if (Array.isArray(init) && init.length === 6) {
15
+ this.a = init[0];
16
+ this.b = init[1];
17
+ this.c = init[2];
18
+ this.d = init[3];
19
+ this.e = init[4];
20
+ this.f = init[5];
21
+ } else {
22
+ this.a = 1;
23
+ this.b = 0;
24
+ this.c = 0;
25
+ this.d = 1;
26
+ this.e = 0;
27
+ this.f = 0;
28
+ }
29
+ }
30
+ translateSelf(tx, ty = 0) {
31
+ this.e = this.a * tx + this.c * ty + this.e;
32
+ this.f = this.b * tx + this.d * ty + this.f;
33
+ return this;
34
+ }
35
+ scaleSelf(sx, sy = sx) {
36
+ this.a *= sx;
37
+ this.b *= sx;
38
+ this.c *= sy;
39
+ this.d *= sy;
40
+ return this;
41
+ }
42
+ };
43
+ }
44
+
45
+ function stubBrowserGlobals() {
46
+ polyfillDOMMatrix();
47
+ }
48
+
3
49
  let resolvedModule;
4
50
  const isNode = globalThis.process?.release?.name === "node";
5
51
  const isBrowser = typeof window !== "undefined";
6
52
  async function getDocumentProxy(data, options = {}) {
7
53
  const { getDocument } = await getResolvedPDFJS();
54
+ let nodeDefaults = {};
55
+ if (isNode) {
56
+ try {
57
+ const base = undefined("pdfjs-dist/package.json");
58
+ nodeDefaults = {
59
+ disableFontFace: true,
60
+ standardFontDataUrl: new URL("./standard_fonts/", base).href
61
+ };
62
+ } catch {
63
+ }
64
+ }
8
65
  const pdf = await getDocument({
9
66
  data,
10
67
  isEvalSupported: false,
11
68
  // See: https://github.com/mozilla/pdf.js/issues/4244#issuecomment-1479534301
12
69
  useSystemFonts: true,
70
+ ...nodeDefaults,
13
71
  ...options
14
72
  }).promise;
15
73
  return pdf;
@@ -24,6 +82,7 @@ async function resolvePDFJSImport(pdfjsResolver, { reload = false } = {}) {
24
82
  if (resolvedModule && !reload) {
25
83
  return;
26
84
  }
85
+ stubBrowserGlobals();
27
86
  if (pdfjsResolver) {
28
87
  try {
29
88
  resolvedModule = await interopDefault(pdfjsResolver());
@@ -122,8 +181,7 @@ async function resolveCanvasModule(canvasImport) {
122
181
  function injectCanvasConstructors() {
123
182
  if (!resolvedCanvasModule)
124
183
  return;
125
- if (typeof globalThis.DOMMatrix === "undefined")
126
- globalThis.DOMMatrix = resolvedCanvasModule.DOMMatrix;
184
+ globalThis.DOMMatrix = resolvedCanvasModule.DOMMatrix;
127
185
  if (typeof globalThis.ImageData === "undefined")
128
186
  globalThis.ImageData = resolvedCanvasModule.ImageData;
129
187
  if (typeof globalThis.Path2D === "undefined")
package/dist/index.d.cts CHANGED
@@ -125,7 +125,7 @@ declare function extractText$1(data: DocumentInitParameters['data'] | PDFDocumen
125
125
  * await definePDFJSModule(() => import('pdfjs-dist'))
126
126
  */
127
127
  declare function definePDFJSModule(pdfjs: () => Promise<any>): Promise<void>;
128
- /** @deprecated Use `definePDFJSModule` instead. */
128
+ /** @deprecated Use `definePDFJSModule` instead. Will be removed in v2. */
129
129
  declare function configureUnPDF(options: {
130
130
  pdfjs?: () => Promise<any>;
131
131
  }): Promise<void>;
@@ -136,6 +136,10 @@ declare function configureUnPDF(options: {
136
136
  * Applies the following defaults:
137
137
  * - `isEvalSupported: false`
138
138
  * - `useSystemFonts: true`
139
+ *
140
+ * In Node.js environments, additionally applies:
141
+ * - `disableFontFace: true`
142
+ * - `standardFontDataUrl` resolved from the local `pdfjs-dist` package
139
143
  */
140
144
  declare function getDocumentProxy(data: DocumentInitParameters['data'], options?: DocumentInitParameters): Promise<PDFDocumentProxy>;
141
145
  declare function getResolvedPDFJS(): Promise<typeof PDFJS>;
package/dist/index.d.mts CHANGED
@@ -125,7 +125,7 @@ declare function extractText$1(data: DocumentInitParameters['data'] | PDFDocumen
125
125
  * await definePDFJSModule(() => import('pdfjs-dist'))
126
126
  */
127
127
  declare function definePDFJSModule(pdfjs: () => Promise<any>): Promise<void>;
128
- /** @deprecated Use `definePDFJSModule` instead. */
128
+ /** @deprecated Use `definePDFJSModule` instead. Will be removed in v2. */
129
129
  declare function configureUnPDF(options: {
130
130
  pdfjs?: () => Promise<any>;
131
131
  }): Promise<void>;
@@ -136,6 +136,10 @@ declare function configureUnPDF(options: {
136
136
  * Applies the following defaults:
137
137
  * - `isEvalSupported: false`
138
138
  * - `useSystemFonts: true`
139
+ *
140
+ * In Node.js environments, additionally applies:
141
+ * - `disableFontFace: true`
142
+ * - `standardFontDataUrl` resolved from the local `pdfjs-dist` package
139
143
  */
140
144
  declare function getDocumentProxy(data: DocumentInitParameters['data'], options?: DocumentInitParameters): Promise<PDFDocumentProxy>;
141
145
  declare function getResolvedPDFJS(): Promise<typeof PDFJS>;
package/dist/index.d.ts CHANGED
@@ -125,7 +125,7 @@ declare function extractText$1(data: DocumentInitParameters['data'] | PDFDocumen
125
125
  * await definePDFJSModule(() => import('pdfjs-dist'))
126
126
  */
127
127
  declare function definePDFJSModule(pdfjs: () => Promise<any>): Promise<void>;
128
- /** @deprecated Use `definePDFJSModule` instead. */
128
+ /** @deprecated Use `definePDFJSModule` instead. Will be removed in v2. */
129
129
  declare function configureUnPDF(options: {
130
130
  pdfjs?: () => Promise<any>;
131
131
  }): Promise<void>;
@@ -136,6 +136,10 @@ declare function configureUnPDF(options: {
136
136
  * Applies the following defaults:
137
137
  * - `isEvalSupported: false`
138
138
  * - `useSystemFonts: true`
139
+ *
140
+ * In Node.js environments, additionally applies:
141
+ * - `disableFontFace: true`
142
+ * - `standardFontDataUrl` resolved from the local `pdfjs-dist` package
139
143
  */
140
144
  declare function getDocumentProxy(data: DocumentInitParameters['data'], options?: DocumentInitParameters): Promise<PDFDocumentProxy>;
141
145
  declare function getResolvedPDFJS(): Promise<typeof PDFJS>;
package/dist/index.mjs CHANGED
@@ -1,13 +1,71 @@
1
+ function polyfillDOMMatrix() {
2
+ if (typeof globalThis.DOMMatrix !== "undefined")
3
+ return;
4
+ globalThis.DOMMatrix = class DOMMatrix {
5
+ a;
6
+ b;
7
+ c;
8
+ d;
9
+ e;
10
+ f;
11
+ constructor(init) {
12
+ if (Array.isArray(init) && init.length === 6) {
13
+ this.a = init[0];
14
+ this.b = init[1];
15
+ this.c = init[2];
16
+ this.d = init[3];
17
+ this.e = init[4];
18
+ this.f = init[5];
19
+ } else {
20
+ this.a = 1;
21
+ this.b = 0;
22
+ this.c = 0;
23
+ this.d = 1;
24
+ this.e = 0;
25
+ this.f = 0;
26
+ }
27
+ }
28
+ translateSelf(tx, ty = 0) {
29
+ this.e = this.a * tx + this.c * ty + this.e;
30
+ this.f = this.b * tx + this.d * ty + this.f;
31
+ return this;
32
+ }
33
+ scaleSelf(sx, sy = sx) {
34
+ this.a *= sx;
35
+ this.b *= sx;
36
+ this.c *= sy;
37
+ this.d *= sy;
38
+ return this;
39
+ }
40
+ };
41
+ }
42
+
43
+ function stubBrowserGlobals() {
44
+ polyfillDOMMatrix();
45
+ }
46
+
1
47
  let resolvedModule;
2
48
  const isNode = globalThis.process?.release?.name === "node";
3
49
  const isBrowser = typeof window !== "undefined";
4
50
  async function getDocumentProxy(data, options = {}) {
5
51
  const { getDocument } = await getResolvedPDFJS();
52
+ let nodeDefaults = {};
53
+ if (isNode) {
54
+ try {
55
+ const base = import.meta.resolve("pdfjs-dist/package.json");
56
+ nodeDefaults = {
57
+ disableFontFace: true,
58
+ standardFontDataUrl: new URL("./standard_fonts/", base).href
59
+ };
60
+ } catch {
61
+ }
62
+ }
6
63
  const pdf = await getDocument({
7
64
  data,
8
65
  isEvalSupported: false,
9
66
  // See: https://github.com/mozilla/pdf.js/issues/4244#issuecomment-1479534301
10
67
  useSystemFonts: true,
68
+ ...nodeDefaults,
11
69
  ...options
12
70
  }).promise;
13
71
  return pdf;
@@ -22,6 +80,7 @@ async function resolvePDFJSImport(pdfjsResolver, { reload = false } = {}) {
22
80
  if (resolvedModule && !reload) {
23
81
  return;
24
82
  }
83
+ stubBrowserGlobals();
25
84
  if (pdfjsResolver) {
26
85
  try {
27
86
  resolvedModule = await interopDefault(pdfjsResolver());
@@ -120,8 +179,7 @@ async function resolveCanvasModule(canvasImport) {
120
179
  function injectCanvasConstructors() {
121
180
  if (!resolvedCanvasModule)
122
181
  return;
123
- if (typeof globalThis.DOMMatrix === "undefined")
124
- globalThis.DOMMatrix = resolvedCanvasModule.DOMMatrix;
182
+ globalThis.DOMMatrix = resolvedCanvasModule.DOMMatrix;
125
183
  if (typeof globalThis.ImageData === "undefined")
126
184
  globalThis.ImageData = resolvedCanvasModule.ImageData;
127
185
  if (typeof globalThis.Path2D === "undefined")