diff --git a/.github/actions/install/action.yml b/.github/actions/install/action.yml new file mode 100644 index 000000000..bf8bc447c --- /dev/null +++ b/.github/actions/install/action.yml @@ -0,0 +1,18 @@ +name: install +description: > + Installs Meteor and NPM dependencies. + +runs: + + using: composite + + steps: + + - name: Install 💾 + id: install + uses: meteor-actions/install@v6 + + - name: Postinstall 🪝 + if: steps.install.outputs.ran-npm-install-hooks != 'true' + shell: bash + run: meteor npm run postinstall diff --git a/.github/workflows/ci:build.yml b/.github/workflows/ci:build.yml index c818ababc..09b984da2 100644 --- a/.github/workflows/ci:build.yml +++ b/.github/workflows/ci:build.yml @@ -34,7 +34,7 @@ jobs: uses: actions/checkout@v4 - name: Install 💾 - uses: meteor-actions/install@v6 + uses: ./.github/actions/install - name: Get Meteor's Node version id: meteor-node diff --git a/.github/workflows/ci:commit-msg.yml b/.github/workflows/ci:commit-msg.yml index a715a858e..9b103a2d3 100644 --- a/.github/workflows/ci:commit-msg.yml +++ b/.github/workflows/ci:commit-msg.yml @@ -24,7 +24,7 @@ jobs: fetch-depth: 0 - name: Install 💾 - uses: meteor-actions/install@v6 + uses: ./.github/actions/install - name: Lint last pushed commit 👕 if: github.event_name == 'push' diff --git a/.github/workflows/ci:lint-config.yml b/.github/workflows/ci:lint-config.yml index bbb329847..eed556ed6 100644 --- a/.github/workflows/ci:lint-config.yml +++ b/.github/workflows/ci:lint-config.yml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v4 - name: Install 💾 - uses: meteor-actions/install@v6 + uses: ./.github/actions/install - name: Lint config 👕 run: meteor npm run lint-config diff --git a/.github/workflows/ci:lint.yml b/.github/workflows/ci:lint.yml index a528689b8..dd3542490 100644 --- a/.github/workflows/ci:lint.yml +++ b/.github/workflows/ci:lint.yml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v4 - name: Install 💾 - uses: meteor-actions/install@v6 + uses: ./.github/actions/install - name: Lint 👕 run: meteor npm run lint diff --git a/.github/workflows/ci:test.yml b/.github/workflows/ci:test.yml index 1e106c326..c00d7103c 100644 --- a/.github/workflows/ci:test.yml +++ b/.github/workflows/ci:test.yml @@ -31,7 +31,7 @@ jobs: key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} - name: Install 💾 - uses: meteor-actions/install@v6 + uses: ./.github/actions/install - name: Cache build 💽 uses: meteor-actions/cache-build@v4 @@ -92,7 +92,7 @@ jobs: key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} - name: Install 💾 - uses: meteor-actions/install@v6 + uses: ./.github/actions/install - name: Cache build 💽 uses: meteor-actions/cache-build@v4 diff --git a/.github/workflows/ci:type-check.yml b/.github/workflows/ci:type-check.yml index f3d5394a8..1cb9d96cb 100644 --- a/.github/workflows/ci:type-check.yml +++ b/.github/workflows/ci:type-check.yml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v4 - name: Install 💾 - uses: meteor-actions/install@v6 + uses: ./.github/actions/install - name: TypeScript check ☑️ run: meteor npm run tsc diff --git a/client/main.tsx b/client/main.tsx index 0637fe632..1fc35d229 100644 --- a/client/main.tsx +++ b/client/main.tsx @@ -1,5 +1,6 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; +// eslint-disable-next-line import/order, import/no-unassigned-import +import './polyfill'; + import React, {StrictMode} from 'react'; import {Meteor} from 'meteor/meteor'; diff --git a/client/polyfill.ts b/client/polyfill.ts new file mode 100644 index 000000000..1a15fb5c6 --- /dev/null +++ b/client/polyfill.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/no-unassigned-import +import 'regenerator-runtime/runtime.js'; diff --git a/codecov.yml b/codecov.yml index d07ddeb3d..0b10e1d15 100644 --- a/codecov.yml +++ b/codecov.yml @@ -2,7 +2,7 @@ coverage: status: project: default: - target: 66% + target: 67% threshold: 2% patch: default: diff --git a/imports/_test/fixtures.ts b/imports/_test/fixtures.ts index a83510e82..f65648c23 100644 --- a/imports/_test/fixtures.ts +++ b/imports/_test/fixtures.ts @@ -88,6 +88,8 @@ export const client = (title, fn) => { let original; const prepare = async () => { + await import('../../client/polyfill'); + original = await forgetHistory(); await cleanup(); }; @@ -113,6 +115,8 @@ export const server = (title, fn) => { }; const prepare = async () => { + await import('../../server/polyfill'); + if (isAppTest()) { await appIsReady(); } @@ -132,6 +136,11 @@ export const server = (title, fn) => { } }; +export const isomorphic = (title, fn) => { + client(title, fn); + server(title, fn); +}; + export const throws = async ( fn: () => Promise, expected: string | RegExp, diff --git a/imports/_test/image.ts b/imports/_test/image.ts new file mode 100644 index 000000000..3a8ce1518 --- /dev/null +++ b/imports/_test/image.ts @@ -0,0 +1,164 @@ +import {assert} from 'chai'; + +import {type Sharp} from 'sharp'; + +import {createContextIso, destroyContextIso} from '../lib/canvas'; +import blobToImage from '../lib/blob/blobToImage'; + +type TypedArray = + | Uint8Array + | Uint8ClampedArray + | Int8Array + | Uint16Array + | Int16Array + | Uint32Array + | Int32Array + | Float32Array + | Float64Array; +type SharpInput = ArrayBuffer | TypedArray; +type S = Sharp | SharpInput; + +type I = HTMLImageElement | ArrayBuffer; + +type Image = S | I; + +const _s = async (x: S): Promise => { + const {default: sharp} = await import('sharp'); + return x instanceof sharp ? (x as Sharp) : sharp(x as SharpInput); +}; + +const _i = async (x: I): Promise => { + if (x instanceof HTMLImageElement) return x; + const blob = new Blob([x], {type: 'image/png'}); + const image = await blobToImage(blob); + return image; +}; + +const _pixels = async (image: HTMLImageElement): Promise => { + const {width, height} = image; + const context = await createContextIso({width, height}); + context.drawImage(image, 0, 0); + const data = context.getImageData(0, 0, width, height).data; + destroyContextIso(context); + return data; +}; + +const _xorClient = ( + a: Uint8ClampedArray, + b: Uint8ClampedArray, +): Uint8ClampedArray => { + const n = a.length; + assert.equal(b.length, n); + const delta = new Uint8ClampedArray(n); + for (let i = 0; i < n; ++i) { + // eslint-disable-next-line no-bitwise + delta[i] = a[i]! ^ b[i]!; + } + + return delta; +}; + +const _diffClient = async (a: Image, b: Image) => { + const _a = await _i(a as I); + const _b = await _i(b as I); + const aPixels = await _pixels(_a); + const bPixels = await _pixels(_b); + return _xorClient(aPixels, bPixels); +}; + +const _diffServer = async (a: Image, b: Image) => { + const _a = await _s(a as S); + const _b = await _s(b as S); + const delta = await _xorServer(_a, _b); + return delta.raw().toBuffer(); +}; + +export const diff = Meteor.isServer ? _diffServer : _diffClient; + +export const assertEqual = async (a: Image, b: Image) => { + const delta = await diff(a, b); + assert( + !delta.some((value, index) => index % 4 !== 3 && value !== 0), + `Input images are not equal.`, + ); +}; + +const _assertSameDimensions = async (a: Sharp, b: Sharp): Promise => { + const { + width: aWidth, + height: aHeight, + channels: aChannels, + hasAlpha: aHasAlpha, + } = await a.metadata(); + const { + width: bWidth, + height: bHeight, + channels: bChannels, + hasAlpha: bHasAlpha, + } = await b.metadata(); + + assert.equal( + aWidth, + bWidth, + `Images have different widths: ${aWidth} !== ${bWidth}`, + ); + + assert.equal( + aHeight, + bHeight, + `Images have different heights: ${aHeight} !== ${bHeight}`, + ); + + assert.equal( + aChannels, + bChannels, + `Images have different number of channels: ${aChannels} !== ${bChannels}`, + ); + + assert.equal( + aHasAlpha, + bHasAlpha, + `Images have different alpha channel settings: ${aHasAlpha} !== ${bHasAlpha}`, + ); +}; + +const _xorServer = async (a: Sharp, b: Sharp): Promise => { + await _assertSameDimensions(a, b); + const _b = await b.toBuffer(); + return a.boolean(_b, 'eor'); +}; + +const _whiteRectanglePNGServer = async (options: { + width: number; + height: number; +}) => { + const {default: sharp} = await import('sharp'); + return sharp({ + create: { + ...options, + channels: 4, + background: {r: 255, g: 255, b: 255, alpha: 0}, + }, + }).png(); +}; + +const _whiteRectanglePNGClient = async ({ + width, + height, +}: { + width: number; + height: number; +}): Promise => { + const context = await createContextIso({width, height}); + context.fillStyle = '#FFFFFF'; + context.fillRect(0, 0, width, height); + const url = context.canvas.toDataURL('image/png'); + destroyContextIso(context); + const img = new Image(); + img.src = url; + return img; +}; + +export const whiteRectanglePNG = Meteor.isServer + ? _whiteRectanglePNGServer + : _whiteRectanglePNGClient; diff --git a/imports/_test/pdf.ts b/imports/_test/pdf.ts new file mode 100644 index 000000000..c7412f3a7 --- /dev/null +++ b/imports/_test/pdf.ts @@ -0,0 +1,31 @@ +import blobToDataURL from '../lib/blob/blobToDataURL'; + +const document = `%PDF-1.0 +1 0 obj<>endobj +2 0 obj<>endobj +3 0 obj<>endobj +xref +0 4 +0000000000 65535 f +0000000009 00000 n +0000000052 00000 n +0000000101 00000 n +trailer<> +startxref +147 +%EOF +`; + +export const randomPDFUint8Array = (): Uint8Array => { + return new TextEncoder().encode(document); +}; + +export const randomPDFBlob = (): Blob => { + const data = randomPDFUint8Array(); + return new Blob([data.buffer], {type: 'application/pdf'}); +}; + +export const randomPDFDataURI = async (): Promise => { + const blob = randomPDFBlob(); + return blobToDataURL(blob); +}; diff --git a/imports/_test/png.ts b/imports/_test/png.ts index 3d1bca70e..4501c40d2 100644 --- a/imports/_test/png.ts +++ b/imports/_test/png.ts @@ -1,3 +1,5 @@ +import pngDataURL from '../lib/png/dataURL'; + const base64Encoded = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; @@ -6,8 +8,7 @@ export const randomPNGBuffer = async () => { return Buffer.from(base64Encoded, 'base64'); }; -export const randomPNGDataURI = (): string => - `data:image/png;base64,${base64Encoded}`; +export const randomPNGDataURI = (): string => pngDataURL(base64Encoded); export const randomPNGResponse = async (): Promise => fetch(randomPNGDataURI()); diff --git a/imports/api/endpoint/allergies/changeColor.tests.ts b/imports/api/endpoint/allergies/changeColor.tests.ts index a523e03f8..d433f01a9 100644 --- a/imports/api/endpoint/allergies/changeColor.tests.ts +++ b/imports/api/endpoint/allergies/changeColor.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import {Allergies} from '../../collection/allergies'; diff --git a/imports/api/endpoint/appointments/beginConsultation.tests.ts b/imports/api/endpoint/appointments/beginConsultation.tests.ts index e466a7583..5a1f1401f 100644 --- a/imports/api/endpoint/appointments/beginConsultation.tests.ts +++ b/imports/api/endpoint/appointments/beginConsultation.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import { diff --git a/imports/api/endpoint/appointments/cancel.tests.ts b/imports/api/endpoint/appointments/cancel.tests.ts index 89cb0a656..75b3e648c 100644 --- a/imports/api/endpoint/appointments/cancel.tests.ts +++ b/imports/api/endpoint/appointments/cancel.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import { diff --git a/imports/api/endpoint/appointments/remove.tests.ts b/imports/api/endpoint/appointments/remove.tests.ts index faf6d3838..766dd61f4 100644 --- a/imports/api/endpoint/appointments/remove.tests.ts +++ b/imports/api/endpoint/appointments/remove.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import { diff --git a/imports/api/endpoint/appointments/reschedule.tests.ts b/imports/api/endpoint/appointments/reschedule.tests.ts index d514e1fc3..e573127a2 100644 --- a/imports/api/endpoint/appointments/reschedule.tests.ts +++ b/imports/api/endpoint/appointments/reschedule.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import {Appointments} from '../../collection/appointments'; diff --git a/imports/api/endpoint/appointments/schedule.tests.ts b/imports/api/endpoint/appointments/schedule.tests.ts index f6cd04a1e..48ae9c27f 100644 --- a/imports/api/endpoint/appointments/schedule.tests.ts +++ b/imports/api/endpoint/appointments/schedule.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import { diff --git a/imports/api/endpoint/appointments/uncancel.tests.ts b/imports/api/endpoint/appointments/uncancel.tests.ts index 0f0005644..20de2d87a 100644 --- a/imports/api/endpoint/appointments/uncancel.tests.ts +++ b/imports/api/endpoint/appointments/uncancel.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import { diff --git a/imports/api/endpoint/availability/next.tests.ts b/imports/api/endpoint/availability/next.tests.ts index 0d1bfa4d7..b6a93eb5e 100644 --- a/imports/api/endpoint/availability/next.tests.ts +++ b/imports/api/endpoint/availability/next.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import addMinutes from 'date-fns/addMinutes'; diff --git a/imports/api/endpoint/books/csv.tests.ts b/imports/api/endpoint/books/csv.tests.ts index 53965ae72..5aed8161f 100644 --- a/imports/api/endpoint/books/csv.tests.ts +++ b/imports/api/endpoint/books/csv.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import startOfYear from 'date-fns/startOfYear'; diff --git a/imports/api/endpoint/books/rename.tests.ts b/imports/api/endpoint/books/rename.tests.ts index 7ef652f7f..1f6039ca8 100644 --- a/imports/api/endpoint/books/rename.tests.ts +++ b/imports/api/endpoint/books/rename.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import invoke from '../invoke'; diff --git a/imports/api/endpoint/consultations/attach.tests.ts b/imports/api/endpoint/consultations/attach.tests.ts index d7977a3f3..f71caaf80 100644 --- a/imports/api/endpoint/consultations/attach.tests.ts +++ b/imports/api/endpoint/consultations/attach.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/consultations/detach.tests.ts b/imports/api/endpoint/consultations/detach.tests.ts index 967502ace..05cc4d5d0 100644 --- a/imports/api/endpoint/consultations/detach.tests.ts +++ b/imports/api/endpoint/consultations/detach.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/consultations/insert.tests.ts b/imports/api/endpoint/consultations/insert.tests.ts index 353d4f491..fedbba506 100644 --- a/imports/api/endpoint/consultations/insert.tests.ts +++ b/imports/api/endpoint/consultations/insert.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/consultations/remove.tests.ts b/imports/api/endpoint/consultations/remove.tests.ts index 249da1c78..a4dbcf2e5 100644 --- a/imports/api/endpoint/consultations/remove.tests.ts +++ b/imports/api/endpoint/consultations/remove.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import {Patients} from '../../collection/patients'; diff --git a/imports/api/endpoint/consultations/restoreAppointment.tests.ts b/imports/api/endpoint/consultations/restoreAppointment.tests.ts index d1609c53d..8dfca3e02 100644 --- a/imports/api/endpoint/consultations/restoreAppointment.tests.ts +++ b/imports/api/endpoint/consultations/restoreAppointment.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import {dropIds, randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/consultations/transfer.tests.ts b/imports/api/endpoint/consultations/transfer.tests.ts index d7eb113be..d3068b1c5 100644 --- a/imports/api/endpoint/consultations/transfer.tests.ts +++ b/imports/api/endpoint/consultations/transfer.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/consultations/update.tests.ts b/imports/api/endpoint/consultations/update.tests.ts index 04e8f41c4..512063474 100644 --- a/imports/api/endpoint/consultations/update.tests.ts +++ b/imports/api/endpoint/consultations/update.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/documents/delete.tests.ts b/imports/api/endpoint/documents/delete.tests.ts index 5b235199b..a9d3de388 100644 --- a/imports/api/endpoint/documents/delete.tests.ts +++ b/imports/api/endpoint/documents/delete.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/documents/fetch.tests.ts b/imports/api/endpoint/documents/fetch.tests.ts index 14e4d1496..b0ed12757 100644 --- a/imports/api/endpoint/documents/fetch.tests.ts +++ b/imports/api/endpoint/documents/fetch.tests.ts @@ -1,7 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; -// eslint-disable-next-line import/no-unassigned-import -import 'core-js/features/string/replace-all'; import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/documents/insert.tests.ts b/imports/api/endpoint/documents/insert.tests.ts index a054b1463..3a08e14cd 100644 --- a/imports/api/endpoint/documents/insert.tests.ts +++ b/imports/api/endpoint/documents/insert.tests.ts @@ -1,7 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; -// eslint-disable-next-line import/no-unassigned-import -import 'core-js/features/string/replace-all'; import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/documents/link.tests.ts b/imports/api/endpoint/documents/link.tests.ts index 6617544df..227a867df 100644 --- a/imports/api/endpoint/documents/link.tests.ts +++ b/imports/api/endpoint/documents/link.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/documents/restore.tests.ts b/imports/api/endpoint/documents/restore.tests.ts index 0893002ed..4f8ad1366 100644 --- a/imports/api/endpoint/documents/restore.tests.ts +++ b/imports/api/endpoint/documents/restore.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/documents/superdelete.tests.ts b/imports/api/endpoint/documents/superdelete.tests.ts index 48398ed67..233810dc8 100644 --- a/imports/api/endpoint/documents/superdelete.tests.ts +++ b/imports/api/endpoint/documents/superdelete.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/documents/unlink.tests.ts b/imports/api/endpoint/documents/unlink.tests.ts index b52b3383b..a0f4272bf 100644 --- a/imports/api/endpoint/documents/unlink.tests.ts +++ b/imports/api/endpoint/documents/unlink.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/patients/attach.tests.ts b/imports/api/endpoint/patients/attach.tests.ts index d83592a1b..1e8b02a67 100644 --- a/imports/api/endpoint/patients/attach.tests.ts +++ b/imports/api/endpoint/patients/attach.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/patients/detach.tests.ts b/imports/api/endpoint/patients/detach.tests.ts index 81dde15bc..15a622639 100644 --- a/imports/api/endpoint/patients/detach.tests.ts +++ b/imports/api/endpoint/patients/detach.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/patients/insert.tests.ts b/imports/api/endpoint/patients/insert.tests.ts index 0faf8d4f8..4ed3a2919 100644 --- a/imports/api/endpoint/patients/insert.tests.ts +++ b/imports/api/endpoint/patients/insert.tests.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; - import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/patients/merge.tests.ts b/imports/api/endpoint/patients/merge.tests.ts index 0d59a5d9d..3bee36570 100644 --- a/imports/api/endpoint/patients/merge.tests.ts +++ b/imports/api/endpoint/patients/merge.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import {randomUserId, server, setLike} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/patients/remove.tests.ts b/imports/api/endpoint/patients/remove.tests.ts index afd7a312f..19a66897e 100644 --- a/imports/api/endpoint/patients/remove.tests.ts +++ b/imports/api/endpoint/patients/remove.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/patients/update.tests.ts b/imports/api/endpoint/patients/update.tests.ts index 6f86474b6..cc7d12897 100644 --- a/imports/api/endpoint/patients/update.tests.ts +++ b/imports/api/endpoint/patients/update.tests.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; - import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/permissions/token/generate.tests.ts b/imports/api/endpoint/permissions/token/generate.tests.ts index 957deafe1..3a7e19457 100644 --- a/imports/api/endpoint/permissions/token/generate.tests.ts +++ b/imports/api/endpoint/permissions/token/generate.tests.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; - import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../../_test/fixtures'; diff --git a/imports/api/endpoint/permissions/token/revoke.tests.ts b/imports/api/endpoint/permissions/token/revoke.tests.ts index 2d3794c23..524c98c81 100644 --- a/imports/api/endpoint/permissions/token/revoke.tests.ts +++ b/imports/api/endpoint/permissions/token/revoke.tests.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; - import {randomUserId, server, throws} from '../../../../_test/fixtures'; import {decode, getPermissionsForToken} from '../../../permissions/token'; diff --git a/imports/api/endpoint/settings/reset.tests.ts b/imports/api/endpoint/settings/reset.tests.ts index ff9ab6124..564fdbfe6 100644 --- a/imports/api/endpoint/settings/reset.tests.ts +++ b/imports/api/endpoint/settings/reset.tests.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; - import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/settings/update.tests.ts b/imports/api/endpoint/settings/update.tests.ts index 8d38cfd82..3d366d14c 100644 --- a/imports/api/endpoint/settings/update.tests.ts +++ b/imports/api/endpoint/settings/update.tests.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; - import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/uploads/delete.tests.ts b/imports/api/endpoint/uploads/delete.tests.ts index 9b307c290..031874f8b 100644 --- a/imports/api/endpoint/uploads/delete.tests.ts +++ b/imports/api/endpoint/uploads/delete.tests.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; - import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/uploads/rename.tests.ts b/imports/api/endpoint/uploads/rename.tests.ts index e0cb42c6f..a25b43fac 100644 --- a/imports/api/endpoint/uploads/rename.tests.ts +++ b/imports/api/endpoint/uploads/rename.tests.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; - import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/endpoint/uploads/restore.tests.ts b/imports/api/endpoint/uploads/restore.tests.ts index 7f71e58a0..c273c42ee 100644 --- a/imports/api/endpoint/uploads/restore.tests.ts +++ b/imports/api/endpoint/uploads/restore.tests.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; - import {assert} from 'chai'; import {randomUserId, server, throws} from '../../../_test/fixtures'; diff --git a/imports/api/transaction/TransactionDriver.tests.ts b/imports/api/transaction/TransactionDriver.tests.ts index 040bd9ca8..0cc91a402 100644 --- a/imports/api/transaction/TransactionDriver.tests.ts +++ b/imports/api/transaction/TransactionDriver.tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import {dropId, dropIds, randomId, server} from '../../_test/fixtures'; diff --git a/imports/app/isAppTest.app-tests.ts b/imports/app/isAppTest.app-tests.ts index 864c23c69..94e919a0c 100644 --- a/imports/app/isAppTest.app-tests.ts +++ b/imports/app/isAppTest.app-tests.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; - import {assert} from 'chai'; import {client, server} from '../_test/fixtures'; diff --git a/imports/app/isAppTest.tests.ts b/imports/app/isAppTest.tests.ts index 68d5f0a8e..14ae6f278 100644 --- a/imports/app/isAppTest.tests.ts +++ b/imports/app/isAppTest.tests.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; - import {assert} from 'chai'; import {client, server} from '../_test/fixtures'; diff --git a/imports/app/isNonAppTest.app-tests.ts b/imports/app/isNonAppTest.app-tests.ts index ac664f2a9..c1ef7a805 100644 --- a/imports/app/isNonAppTest.app-tests.ts +++ b/imports/app/isNonAppTest.app-tests.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; - import {assert} from 'chai'; import {client, server} from '../_test/fixtures'; diff --git a/imports/app/isNonAppTest.tests.ts b/imports/app/isNonAppTest.tests.ts index 1da236d33..09ccce351 100644 --- a/imports/app/isNonAppTest.tests.ts +++ b/imports/app/isNonAppTest.tests.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; - import {assert} from 'chai'; import {client, server} from '../_test/fixtures'; diff --git a/imports/app/isProduction.app-tests.ts b/imports/app/isProduction.app-tests.ts index e4bba1e4f..0fb8b19c1 100644 --- a/imports/app/isProduction.app-tests.ts +++ b/imports/app/isProduction.app-tests.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; - import {assert} from 'chai'; import {client, server} from '../_test/fixtures'; diff --git a/imports/app/isProduction.tests.ts b/imports/app/isProduction.tests.ts index e4bba1e4f..0fb8b19c1 100644 --- a/imports/app/isProduction.tests.ts +++ b/imports/app/isProduction.tests.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; - import {assert} from 'chai'; import {client, server} from '../_test/fixtures'; diff --git a/imports/app/isTest.app-tests.ts b/imports/app/isTest.app-tests.ts index c083323ba..f054b4532 100644 --- a/imports/app/isTest.app-tests.ts +++ b/imports/app/isTest.app-tests.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; - import {assert} from 'chai'; import {client, server} from '../_test/fixtures'; diff --git a/imports/app/isTest.tests.ts b/imports/app/isTest.tests.ts index c083323ba..f054b4532 100644 --- a/imports/app/isTest.tests.ts +++ b/imports/app/isTest.tests.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; - import {assert} from 'chai'; import {client, server} from '../_test/fixtures'; diff --git a/imports/lib/blob/blobFromDataURL.ts b/imports/lib/blob/blobFromDataURL.ts new file mode 100644 index 000000000..1ab1e4a23 --- /dev/null +++ b/imports/lib/blob/blobFromDataURL.ts @@ -0,0 +1,6 @@ +const blobFromDataURL = async (url: string): Promise => { + const response = await fetch(url); + return response.blob(); +}; + +export default blobFromDataURL; diff --git a/imports/lib/blob/blobToDataURL.ts b/imports/lib/blob/blobToDataURL.ts new file mode 100644 index 000000000..b6cbcaecf --- /dev/null +++ b/imports/lib/blob/blobToDataURL.ts @@ -0,0 +1,39 @@ +const _blobToDataURLClient = async (blob: Blob): Promise => + new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.addEventListener('load', (_e) => { + resolve(reader.result as string); + }); + + reader.addEventListener('error', (_e) => { + reject(reader.error); + }); + + reader.addEventListener('abort', (_e) => { + reject(new Error('Read aborted')); + }); + reader.readAsDataURL(blob); + }); + +const _blobToDataURLServer = async (blob: Blob): Promise => { + const mimeType = blob.type; + if (mimeType === '') { + throw new Error('unknown mime-type'); + } + + const arrayBuffer = await blob.arrayBuffer(); + + const {Buffer} = await import('buffer'); + const buffer = Buffer.from(arrayBuffer); + + const base64 = buffer.toString('base64'); + + const {default: dataURL} = await import('../dataURL'); + return dataURL(mimeType, base64); +}; + +const blobToDataURL = Meteor.isServer + ? _blobToDataURLServer + : _blobToDataURLClient; + +export default blobToDataURL; diff --git a/imports/lib/blob/blobToImage.ts b/imports/lib/blob/blobToImage.ts new file mode 100644 index 000000000..fec3c0e65 --- /dev/null +++ b/imports/lib/blob/blobToImage.ts @@ -0,0 +1,22 @@ +const blobToImage = async (blob: Blob): Promise => + new Promise((resolve, reject) => { + const url = URL.createObjectURL(blob); + const img = new Image(); + img.addEventListener('load', () => { + URL.revokeObjectURL(url); + resolve(img); + }); + + img.addEventListener('error', (error) => { + URL.revokeObjectURL(url); + reject(error); + }); + + img.addEventListener('abort', (_e) => { + reject(new Error(`Image load aborted for ${url}`)); + }); + + img.src = url; + }); + +export default blobToImage; diff --git a/imports/lib/blob/dataURL.tests.ts b/imports/lib/blob/dataURL.tests.ts new file mode 100644 index 000000000..700ce08d4 --- /dev/null +++ b/imports/lib/blob/dataURL.tests.ts @@ -0,0 +1,19 @@ +import {assert} from 'chai'; + +import {isomorphic} from '../../_test/fixtures'; +import {randomPNGDataURI} from '../../_test/png'; + +import blobFromDataURL from './blobFromDataURL'; +import blobToDataURL from './blobToDataURL'; + +isomorphic(__filename, () => { + it('should allow to convert back and forth from a dataURL', async () => { + const url = randomPNGDataURI(); + + const blob = await blobFromDataURL(url); + assert.instanceOf(blob, Blob); + + const result = await blobToDataURL(blob); + assert.equal(result, url); + }); +}); diff --git a/imports/lib/canvas.ts b/imports/lib/canvas.ts new file mode 100644 index 000000000..40e4fc06b --- /dev/null +++ b/imports/lib/canvas.ts @@ -0,0 +1,50 @@ +import {type Canvas} from 'canvas/types'; + +export { + type Canvas, + type JpegConfig, + type PdfConfig, + type PngConfig, +} from 'canvas/types'; + +type CreateCanvasOptions = { + width: number; + height: number; +}; + +const createCanvasIso = async ({ + width, + height, +}: CreateCanvasOptions): Promise => { + if (Meteor.isServer) { + const {createCanvas} = await import('canvas'); + return createCanvas(width, height); + } + + const browserCanvas = document.createElement('canvas'); + browserCanvas.width = width; + browserCanvas.height = height; + return browserCanvas; +}; + +export const destroyCanvasIso = (canvas: HTMLCanvasElement | Canvas) => { + canvas.width = 0; + canvas.height = 0; + if (Meteor.isClient) { + (canvas as HTMLCanvasElement).remove(); + } +}; + +type CreateContextOptions = {} & CreateCanvasOptions; + +export const createContextIso = async ( + options: CreateContextOptions, +): Promise => { + return createCanvasIso(options).then( + (canvas) => canvas.getContext('2d') as CanvasRenderingContext2D, + ); +}; + +export const destroyContextIso = (context: CanvasRenderingContext2D) => { + destroyCanvasIso(context.canvas); +}; diff --git a/imports/lib/dataURL.ts b/imports/lib/dataURL.ts new file mode 100644 index 000000000..0da73dd30 --- /dev/null +++ b/imports/lib/dataURL.ts @@ -0,0 +1,3 @@ +const dataURL = (mimeType: string, base64: string) => + `data:${mimeType};base64,${base64}`; +export default dataURL; diff --git a/imports/lib/pdf/pdf.tests.ts b/imports/lib/pdf/pdf.tests.ts new file mode 100644 index 000000000..805d3c0c9 --- /dev/null +++ b/imports/lib/pdf/pdf.tests.ts @@ -0,0 +1,18 @@ +import {client, server} from '../../_test/fixtures'; +import {randomPDFUint8Array} from '../../_test/pdf'; + +import {fetchPDF} from './pdf'; + +client(__filename, () => { + it('should work on the client', async () => { + const data = randomPDFUint8Array(); + await fetchPDF({data}); + }); +}); + +server(__filename, () => { + it('should work on the server', async () => { + const data = randomPDFUint8Array(); + await fetchPDF({data}); + }); +}); diff --git a/imports/lib/pdf/pdfthumbmails.tests.ts b/imports/lib/pdf/pdfthumbmails.tests.ts new file mode 100644 index 000000000..1532d5ebc --- /dev/null +++ b/imports/lib/pdf/pdfthumbmails.tests.ts @@ -0,0 +1,144 @@ +import {Buffer} from 'buffer'; +import {Readable} from 'stream'; + +import {assert} from 'chai'; + +import {client, server, throws} from '../../_test/fixtures'; +import {randomPDFUint8Array, randomPDFDataURI} from '../../_test/pdf'; +import { + assertEqual as assertEqualImages, + whiteRectanglePNG, +} from '../../_test/image'; + +import blobFromDataURL from '../blob/blobFromDataURL'; +import blobToBuffer from '../blob/blobToBuffer'; +import streamToBuffer from '../stream/streamToBuffer'; + +import { + thumbnailDataURL, + thumbnailStream, + thumbnailBlob, + thumbnailBuffer, +} from './pdfthumbnails'; + +const width = 200; +const height = 200; + +client(__filename, () => { + it('thumbnailDataURL should work', async () => { + const pdfDataURL = await randomPDFDataURI(); + const pngDataURL = await thumbnailDataURL( + pdfDataURL, + {minWidth: width, minHeight: height}, + {type: 'image/png'}, + ); + + assert.typeOf(pngDataURL, 'string'); + + const buffer = await blobFromDataURL(pngDataURL).then(blobToBuffer); + const expected = await whiteRectanglePNG({width, height}); + + await assertEqualImages(buffer, expected); + }); + + it('thumbnailBlob should work', async () => { + const data = randomPDFUint8Array(); + const blob = await thumbnailBlob( + {data}, + {minWidth: width, minHeight: height}, + {type: 'image/png'}, + ); + assert.instanceOf(blob, Blob); + + const buffer = await blobToBuffer(blob); + const expected = await whiteRectanglePNG({width, height}); + + await assertEqualImages(buffer, expected); + }); + + it('thumbnailBuffer should NOT be implemented', async () => { + const data = randomPDFUint8Array(); + await throws( + async () => + thumbnailBuffer( + {data}, + {minWidth: width, minHeight: height}, + {type: 'image/png'}, + ), + /not implemented/i, + ); + }); + + it('thumbnailStream should NOT be implemented', async () => { + const data = randomPDFUint8Array(); + await throws( + async () => + thumbnailStream( + {data}, + {minWidth: width, minHeight: height}, + {type: 'image/png'}, + ), + /not implemented/i, + ); + }); +}); + +server(__filename, () => { + it('thumbnailDataURL should NOT be implemented', async () => { + const url = await randomPDFDataURI(); + + await throws( + async () => + thumbnailDataURL( + url, + {minWidth: width, minHeight: height}, + {type: 'image/png'}, + ), + /not implemented/i, + ); + }); + + it('thumbnailBlob should work', async () => { + const data = randomPDFUint8Array(); + + await throws( + async () => + thumbnailBlob( + {data}, + {minWidth: width, minHeight: height}, + {type: 'image/png'}, + ), + /not implemented/i, + ); + }); + + it('thumbnailBuffer should work', async () => { + const data = randomPDFUint8Array(); + const buffer = await thumbnailBuffer( + {data}, + {minWidth: width, minHeight: height}, + {type: 'image/png'}, + ); + assert.instanceOf(buffer, Buffer); + + const expected = await whiteRectanglePNG({width, height}); + + await assertEqualImages(buffer, expected); + }); + + it('thumbnailStream should work', async () => { + const data = randomPDFUint8Array(); + const stream = await thumbnailStream( + {data}, + {minWidth: width, minHeight: height}, + {type: 'image/png'}, + ); + assert.instanceOf(stream, Readable); + + const buffer = await streamToBuffer(stream); + + const expected = await whiteRectanglePNG({width, height}); + + await assertEqualImages(buffer, expected); + }); +}); diff --git a/imports/lib/pdf/pdfthumbnails.ts b/imports/lib/pdf/pdfthumbnails.ts index 826213eea..62f8d063c 100644 --- a/imports/lib/pdf/pdfthumbnails.ts +++ b/imports/lib/pdf/pdfthumbnails.ts @@ -2,15 +2,18 @@ import {type Buffer} from 'buffer'; import {type Readable} from 'stream'; +import addDays from 'date-fns/addDays'; + +import {cache as lru, type IndexedDBPersistedLRUCache} from '../cache/lru'; + import { type Canvas, type JpegConfig, type PdfConfig, type PngConfig, -} from 'canvas/types'; -import addDays from 'date-fns/addDays'; - -import {cache as lru, type IndexedDBPersistedLRUCache} from '../cache/lru'; + createContextIso, + destroyContextIso, +} from '../canvas'; import {type DocumentInitParameters, type PageViewport, fetchPDF} from './pdf'; @@ -22,44 +25,6 @@ if (Meteor.isClient) { }); } -type CreateCanvasOptions = { - width: number; - height: number; -}; - -const createCanvasIso = async ({ - width, - height, -}: CreateCanvasOptions): Promise => { - if (Meteor.isServer) { - const {createCanvas} = await import('canvas'); - return createCanvas(width, height); - } - - const browserCanvas = document.createElement('canvas'); - browserCanvas.width = width; - browserCanvas.height = height; - return browserCanvas; -}; - -const destroyCanvasIso = (canvas: HTMLCanvasElement | Canvas) => { - canvas.width = 0; - canvas.height = 0; - if (Meteor.isClient) { - (canvas as HTMLCanvasElement).remove(); - } -}; - -type CreateContextOptions = {} & CreateCanvasOptions; - -const createContextIso = async ( - options: CreateContextOptions, -): Promise => { - return createCanvasIso(options).then( - (canvas) => canvas.getContext('2d') as CanvasRenderingContext2D, - ); -}; - type RenderContext = { canvasContext: CanvasRenderingContext2D; viewport: PageViewport; @@ -91,7 +56,7 @@ const createRenderContextIso = async ( }; const destroyRenderContextIso = (renderContext: RenderContext) => { - destroyCanvasIso(renderContext.canvasContext.canvas); + destroyContextIso(renderContext.canvasContext); // @ts-expect-error This is for garbage collection. renderContext.canvasContext = null; }; @@ -161,8 +126,12 @@ export const thumbnailDataURL = async ( const toBlob = async ( canvas: HTMLCanvasElement, {type, quality}: RenderOptions, -) => - new Promise((resolve, reject) => { +) => { + if (Meteor.isServer) { + throw new Error('not implemented'); + } + + return new Promise((resolve, reject) => { canvas.toBlob( (blob: Blob | null) => { if (blob === null) { @@ -175,6 +144,7 @@ const toBlob = async ( quality, ); }); +}; export const thumbnailBlob = async ( document: DocumentInitParameters, @@ -232,6 +202,10 @@ export const thumbnailBuffer = async ( pageOptions: PageOptions, canvasRenderOptions: CanvasRenderOptions, ) => { + if (Meteor.isClient) { + throw new Error('not implemented'); + } + const renderContext = await thumbnailRender(document, pageOptions); const canvas = renderContext.canvasContext.canvas as unknown as Canvas; const buffer = await toBuffer(canvas, canvasRenderOptions); @@ -244,6 +218,10 @@ export const thumbnailStream = async ( pageOptions: PageOptions, {type = 'image/png', config}: CanvasRenderOptions, ): Promise => { + if (Meteor.isClient) { + throw new Error('not implemented'); + } + const renderContext = await thumbnailRender(document, pageOptions); const canvas = renderContext.canvasContext.canvas as unknown as Canvas; let stream: Readable; diff --git a/imports/lib/png/dataURL.ts b/imports/lib/png/dataURL.ts index 0ff937c9c..b8a265a53 100644 --- a/imports/lib/png/dataURL.ts +++ b/imports/lib/png/dataURL.ts @@ -1,2 +1,4 @@ -const dataURL = (base64: string) => `data:image/png;base64,${base64}`; +import _dataURL from '../dataURL'; + +const dataURL = (base64: string) => _dataURL('image/png', base64); export default dataURL; diff --git a/imports/ui/App.tests.tsx b/imports/ui/App.tests.tsx index e6cd1ec52..e6c85d437 100644 --- a/imports/ui/App.tests.tsx +++ b/imports/ui/App.tests.tsx @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import React from 'react'; import {render, waitForElementToBeRemoved} from '@testing-library/react'; diff --git a/package-lock.json b/package-lock.json index 02b936593..226876a5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2448,6 +2448,16 @@ "rimraf": "^3.0.2", "semver": "^7.3.5", "tar": "^6.1.11" + }, + "dependencies": { + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "requires": { + "whatwg-url": "^5.0.0" + } + } } }, "@mongodb-js/saslprep": { @@ -6766,6 +6776,11 @@ "integrity": "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==", "dev": true }, + "data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==" + }, "data-view-buffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", @@ -7376,6 +7391,17 @@ "enquirer": "^2.3.6", "meow": "^6.1.1", "node-fetch": "^2.6.1" + }, + "dependencies": { + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + } } }, "emoji-regex": { @@ -9002,6 +9028,15 @@ "pend": "~1.2.0" } }, + "fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "requires": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + } + }, "fflate": { "version": "0.4.8", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", @@ -9276,6 +9311,14 @@ "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", "dev": true }, + "formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "requires": { + "fetch-blob": "^3.1.2" + } + }, "formidable": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.1.tgz", @@ -12565,12 +12608,19 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" + }, "node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "requires": { - "whatwg-url": "^5.0.0" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" } }, "node-gyp": { @@ -13617,12 +13667,6 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, - "path2d-polyfill": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz", - "integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==", - "optional": true - }, "pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", @@ -13630,12 +13674,12 @@ "dev": true }, "pdfjs-dist": { - "version": "3.11.174", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz", - "integrity": "sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA==", + "version": "3.1.81", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.1.81.tgz", + "integrity": "sha512-hZHVVbjU2Ac1VYyPFrg9fBcyS7EEdB8YFy5upk6LmnsXl10WxAavdiViGWi2C/xK0GZObEpSSJU1VnoF9t8n9w==", "requires": { - "canvas": "^2.11.2", - "path2d-polyfill": "^2.0.1" + "canvas": "^2.10.2", + "web-streams-polyfill": "^3.2.1" } }, "pend": { @@ -16444,6 +16488,11 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, + "web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==" + }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index 6a97ef2d0..6b967cc07 100644 --- a/package.json +++ b/package.json @@ -113,9 +113,10 @@ "medidoc": "1.1.0", "mem": "^8.1.1", "meteor-node-stubs": "^1.2.5", + "node-fetch": "^3.3.2", "notistack": "^3.0.1", "papaparse": "^5.4.1", - "pdfjs-dist": "~3.11.174", + "pdfjs-dist": "~3.1.81", "qrcode.react": "^3.1.0", "rate-limiter-flexible": "^3.0.0", "react": "^18.2.0", diff --git a/server/api/healthcheck/index.app-tests.ts b/server/api/healthcheck/index.app-tests.ts index a665270ee..b904d96b6 100644 --- a/server/api/healthcheck/index.app-tests.ts +++ b/server/api/healthcheck/index.app-tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import request from 'supertest'; diff --git a/server/api/ics/index.app-tests.ts b/server/api/ics/index.app-tests.ts index 7d0b04af1..7818df423 100644 --- a/server/api/ics/index.app-tests.ts +++ b/server/api/ics/index.app-tests.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line import/no-unassigned-import -import 'regenerator-runtime/runtime.js'; import {assert} from 'chai'; import request from 'supertest'; diff --git a/server/polyfill.ts b/server/polyfill.ts index 7678d9eca..0125dead4 100644 --- a/server/polyfill.ts +++ b/server/polyfill.ts @@ -3,3 +3,6 @@ import 'regenerator-runtime/runtime.js'; // eslint-disable-next-line import/no-unassigned-import import 'core-js/features/string/replace-all'; + +// eslint-disable-next-line import/no-unassigned-import +import './polyfill/fetch'; diff --git a/server/polyfill/fetch.ts b/server/polyfill/fetch.ts new file mode 100644 index 000000000..6dc26f591 --- /dev/null +++ b/server/polyfill/fetch.ts @@ -0,0 +1,25 @@ +// SEE +// https://github.com/node-fetch/node-fetch/blob/8b3320d2a7c07bce4afc6b2bf6c3bbddda85b01f/README.md#providing-global-access + +import process from 'process'; + +import fetch, {Blob, Headers, Request, Response} from 'node-fetch'; + +import semver from 'semver'; + +if (semver.gte(process.version, '18.0.0')) { + // eslint-disable-next-line unicorn/prefer-module + throw new Error(`Remove node-fetch polyfill located at '${__filename}'.`); +} + +if (Meteor.isServer && !globalThis.fetch) { + // @ts-expect-error fetch polyfill has incorrect type. + globalThis.fetch = fetch; + globalThis.Blob = Blob; + // @ts-expect-error Headers polyfill has incorrect type. + globalThis.Headers = Headers; + // @ts-expect-error Request polyfill has incorrect type. + globalThis.Request = Request; + // @ts-expect-error Response polyfill has incorrect type. + globalThis.Response = Response; +}