From 1a74cd0c14765383ee622d6a37d3fcc5d4c991a9 Mon Sep 17 00:00:00 2001 From: Jannis Leifeld Date: Tue, 13 Jun 2023 16:02:55 +0200 Subject: [PATCH] NEXT-28243 - improve dataset handling with selector --- e2e/channel.spec.ts | 4 +- package-lock.json | 28 ++-- .../error-handling/error-factory.ts | 2 + src/_internals/sdkVersion.js | 7 + src/_internals/validator/index.ts | 11 +- src/channel.spec.ts | 47 ++++++- src/channel.ts | 127 +++++++++++++++--- src/data/_internals/selectData.ts | 76 +++++++++++ src/data/composables/useSharedState.spec.ts | 27 +++- src/data/index.ts | 64 +++++++-- src/messages.types.ts | 5 +- src/privileges/privilege-resolver.ts | 12 +- tsconfig.json | 3 +- vite.config.ts | 2 +- 14 files changed, 360 insertions(+), 55 deletions(-) create mode 100644 src/_internals/sdkVersion.js create mode 100644 src/data/_internals/selectData.ts diff --git a/e2e/channel.spec.ts b/e2e/channel.spec.ts index 055df8a2..be3c11a7 100644 --- a/e2e/channel.spec.ts +++ b/e2e/channel.spec.ts @@ -36,6 +36,8 @@ test.describe('Main communication test', () => { // check if sourceRegistry contains the window const sourceRegistryLength = await mainFrame.evaluate(() => { + console.log('window._swsdk', window._swsdk) + return [...window._swsdk.sourceRegistry].length; }) @@ -651,7 +653,7 @@ test.describe('Privilege tests', () => { } }); - expect(response.errorMessage).toEqual(`Error: Your app is missing the privileges create:product, delete:product, update:product, create:product, delete:product, update:product, create:manufacturer, delete:manufacturer, read:manufacturer, update:manufacturer for action "_collectionTest".`); + expect(response.errorMessage).toEqual(`Error: Your app is missing the privileges create:product, delete:product, update:product, create:manufacturer, delete:manufacturer, read:manufacturer, update:manufacturer for action "_collectionTest".`); expect(response.isMissingPrivilesErrorInstance).toBe(true); }); diff --git a/package-lock.json b/package-lock.json index 6dadd29a..abc3de02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@shopware-ag/admin-extension-sdk", - "version": "3.0.8", + "version": "3.0.10", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@shopware-ag/admin-extension-sdk", - "version": "3.0.8", + "version": "3.0.10", "license": "MIT", "dependencies": { "localforage": "^1.10.0", @@ -2419,9 +2419,9 @@ "dev": true }, "node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", + "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -5069,9 +5069,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -13296,9 +13296,9 @@ "dev": true }, "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", + "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", "dev": true }, "acorn-globals": { @@ -15179,9 +15179,9 @@ "dev": true }, "fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", diff --git a/src/_internals/error-handling/error-factory.ts b/src/_internals/error-handling/error-factory.ts index 32c800ea..53b93326 100644 --- a/src/_internals/error-handling/error-factory.ts +++ b/src/_internals/error-handling/error-factory.ts @@ -18,6 +18,8 @@ export default function createError(type: keyof ShopwareMessageTypes, e: unknown const missingPrivilegeErrors = (e as any).response.data.errors .filter((error: { code: string }) => error.code === 'FRAMEWORK__MISSING_PRIVILEGE_ERROR') as { detail: string }[]; + console.log('missingPrivilegeErrors', missingPrivilegeErrors) + const missingPrivileges: privilegeString[] = []; missingPrivilegeErrors.forEach((mpe) => { diff --git a/src/_internals/sdkVersion.js b/src/_internals/sdkVersion.js new file mode 100644 index 00000000..bbb87176 --- /dev/null +++ b/src/_internals/sdkVersion.js @@ -0,0 +1,7 @@ +/** + * JS file is needed because TypeScript can't use JSON imports + * for UMD builds. + */ +import { version } from '../../package.json'; + +export default version; diff --git a/src/_internals/validator/index.ts b/src/_internals/validator/index.ts index 2cc9b56c..a2741381 100644 --- a/src/_internals/validator/index.ts +++ b/src/_internals/validator/index.ts @@ -22,6 +22,7 @@ export default function validate({ const extension = findExtensionByBaseUrl(origin); if (!extension) { + console.warn(`No extension found for origin ${origin}`); return null; } @@ -32,11 +33,17 @@ export default function validate({ if (key === '__type__' && ['__EntityCollection__', '__Entity__'].includes(value as string)) { const entityName = parentEntry.__entityName__ as string; + if (!entityName) { + return; + } + [...privilegesToCheck].sort().forEach(privilege => { const permissionsForPrivilege = extension.permissions[privilege]; if ( - !permissionsForPrivilege || - !permissionsForPrivilege.includes(entityName) + (!permissionsForPrivilege || + !permissionsForPrivilege.includes(entityName)) + && + !privilegeErrors.includes(`${privilege}:${entityName}`) ) { privilegeErrors.push(`${privilege}:${entityName}`); } diff --git a/src/channel.spec.ts b/src/channel.spec.ts index 6cb17ad9..d7b76b08 100644 --- a/src/channel.spec.ts +++ b/src/channel.spec.ts @@ -1,11 +1,56 @@ import flushPromises from 'flush-promises'; -import { send, handle, createSender, createHandler, subscribe, publish, setExtensions } from './channel'; +import type { + send as sendType, + handle as handleType, + createSender as createSenderType, + createHandler as createHandlerType, + subscribe as subscribeType, + publish as publishType, + setExtensions as setExtensionsType, +} from './channel'; import MissingPrivilegesError from './privileges/missing-privileges-error'; // Channel send timout + 1000 jest.setTimeout(8000); +let send: typeof sendType; +let handle: typeof handleType; +let createSender: typeof createSenderType; +let createHandler: typeof createHandlerType; +let subscribe: typeof subscribeType; +let publish: typeof publishType; +let setExtensions: typeof setExtensionsType; + describe('Test the channel bridge from iFrame to admin', () => { + beforeAll(async () => { + window.addEventListener('message', (event: MessageEvent) => { + if (event.origin === '') { + event.stopImmediatePropagation(); + const eventWithOrigin: MessageEvent = new MessageEvent('message', { + data: event.data, + origin: window.location.href, + }); + window.dispatchEvent(eventWithOrigin); + } + }); + + const channel = await import('./channel'); + send = channel.send; + handle = channel.handle; + createSender = channel.createSender; + createHandler = channel.createHandler; + subscribe = channel.subscribe; + publish = channel.publish; + setExtensions = channel.setExtensions; + + setExtensions({ + 'test-extension': { + baseUrl: 'http://localhost', + permissions: {}, + }, + }); + }); + beforeEach(() => { // reset extensions setExtensions({}); diff --git a/src/channel.ts b/src/channel.ts index 67a023b7..eee06925 100644 --- a/src/channel.ts +++ b/src/channel.ts @@ -8,6 +8,11 @@ import MissingPrivilegesError from './privileges/missing-privileges-error'; import SerializerFactory from './_internals/serializer'; import createError from './_internals/error-handling/error-factory'; import validate from './_internals/validator/index'; +import type { datasetRegistration } from './data'; +import { selectData } from './data/_internals/selectData'; +import sdkVersion from './_internals/sdkVersion'; + +const packageVersion = sdkVersion as string; const { serialize, deserialize } = SerializerFactory({ handle: handle, @@ -18,10 +23,18 @@ export type extensions = { [key: string]: extension, } -export let adminExtensions: extensions = {}; +export const adminExtensions: extensions = {}; export function setExtensions(extensions: extensions): void { - adminExtensions = extensions; + Object.entries(extensions).forEach(([key, value]) => { + // @ts-expect-error = we fill up the values later + adminExtensions[key] = {}; + + Object.entries(value).forEach(([valueKey, valueContent]) => { + // @ts-expect-error = we defined the key beforehand + adminExtensions[key][valueKey] = valueContent; + }); + }); } /** @@ -64,6 +77,14 @@ export type ShopwareMessageResponseData = new Set(); + +const subscriberRegistry: Set<{ + id: string, + selectors: string[] | undefined, + source: Window, + origin: string, }> = new Set(); /** @@ -367,10 +388,19 @@ export function handle export function publish( type: MESSAGE_TYPE, data: ShopwareMessageTypes[MESSAGE_TYPE]['responseType'], + sources: { + source: Window, + origin: string, + sdkVersion: string | undefined, + }[] = [...sourceRegistry].map(({source, origin, sdkVersion}) => ({ + source, + origin, + sdkVersion, + })) ) :void { - [...sourceRegistry].forEach(({source, origin}) => { + sources.forEach(({ source, origin }) => { // Disable error handling because not every window need to react to the data // eslint-disable-next-line @typescript-eslint/no-empty-function return send(type, data, source, origin).catch(() => {}); @@ -421,7 +451,9 @@ export function createSender */ export function createHandler(messageType: MESSAGE_TYPE) { // eslint-disable-next-line @typescript-eslint/explicit-function-return-type - return (method: (data: MessageDataType) => Promise | ShopwareMessageTypes[MESSAGE_TYPE]['responseType']) => { + return (method: (data: MessageDataType, additionalInformation: { + _event_: MessageEvent, + }) => Promise | ShopwareMessageTypes[MESSAGE_TYPE]['responseType']) => { return handle(messageType, method); }; } @@ -457,7 +489,7 @@ const datasets = new Map(); (async (): Promise => { // Handle registrations at current window - handle('__registerWindow__', (_, additionalOptions) => { + handle('__registerWindow__', ({ sdkVersion }, additionalOptions) => { let source: Window | undefined; let origin: string | undefined; @@ -472,35 +504,82 @@ const datasets = new Map(); sourceRegistry.add({ source, origin, + sdkVersion, }); + }); - // Register all existing datasets for apps that come to late for the "synchronous" registration - datasets.forEach((dataset, id ) => { - // eslint-disable-next-line @typescript-eslint/no-empty-function - send('datasetSubscribe', {id, data: dataset}, source, origin).catch(() => {}); + handle('datasetSubscribeRegistration', (data, { _event_ }) => { + let source: Window | undefined; + let origin: string | undefined; + + if (_event_.source) { + source = _event_.source as Window; + origin = _event_.origin; + } else { + source = window; + origin = window.origin; + } + + subscriberRegistry.add({ + id: data.id, + source: source, + origin: origin, + selectors: data.selectors, }); - }); - // New dataset registered - handle('datasetRegistration', (data) => { - datasets.set(data.id, data.data); + // When initial data exists directly send it to the subscriber + const dataset = datasets.get(data.id); - publish('datasetSubscribe', data); + if (dataset) { + const selectedData = selectData(dataset, data.selectors, 'datasetSubscribe', origin); - return { - id: data.id, - data: data.data, - }; - }); + if (selectedData instanceof MissingPrivilegesError) { + console.error(selectedData); + return; + } - handle('datasetSubscribe', (data) => { - return datasets.get(data.id) ?? null; + void send('datasetSubscribe', { + id: data.id, + data: selectedData, + selectors: data.selectors, + }, source, origin); + } }); // Register at parent window - await send('__registerWindow__', {}); + await send('__registerWindow__', { + sdkVersion: packageVersion, + }); })().catch((e) => console.error(e)); +// New dataset registered +export async function processDataRegistration(data: Omit): Promise { + datasets.set(data.id, data.data); + + // Only publish whole data to sources that don't have a sdkVersion (for backwards compatibility) + publish('datasetSubscribe', data, [ + ...[...sourceRegistry].filter(({ sdkVersion }) => !sdkVersion), + ]); + + // Publish selected data to sources that are inside the subscriberRegistry + subscriberRegistry.forEach(({ id, selectors, source, origin }) => { + if (id !== data.id) { + return; + } + + const selectedData = selectData(data.data, selectors, 'datasetSubscribe', origin); + + if (selectedData instanceof MissingPrivilegesError) { + console.error(selectedData); + } + + // eslint-disable-next-line @typescript-eslint/no-empty-function + send('datasetSubscribe', { id, data: selectedData, selectors }, source, origin).catch(() => {}); + }); + + return Promise.resolve(); +} + /** * Add utils to global window object for * testing @@ -509,14 +588,18 @@ const datasets = new Map(); interface Window { _swsdk: { sourceRegistry: typeof sourceRegistry, + subscriberRegistry: typeof subscriberRegistry, datasets: typeof datasets, + adminExtensions: typeof adminExtensions, }, } } window._swsdk = { sourceRegistry, + subscriberRegistry, datasets, + adminExtensions, }; /** diff --git a/src/data/_internals/selectData.ts b/src/data/_internals/selectData.ts new file mode 100644 index 00000000..63ed28c0 --- /dev/null +++ b/src/data/_internals/selectData.ts @@ -0,0 +1,76 @@ +import { toPath, get } from 'lodash'; +import type { ShopwareMessageTypes } from '../../messages.types'; +import MissingPrivilegesError from '../../privileges/missing-privileges-error'; +import type { privilegeString } from '../../privileges/privilege-resolver'; +import { findExtensionByBaseUrl } from '../../privileges/privilege-resolver'; + +export function selectData( + sourceData: unknown, + selectors?: string[], + messageType: keyof ShopwareMessageTypes = 'datasetSubscribe', + origin?: string, +): unknown { + const extension = findExtensionByBaseUrl(origin ?? ''); + const permissionErrors: Array = []; + + if (!selectors) { + return sourceData; + } + + const selectedData = selectors.reduce<{ + [key: string]: unknown, + }>((acc, selector) => { + // Check permissions for path entries + ['', ...toPath(selector)].forEach((path) => { + const value = path !== '' ? get(sourceData, path) as unknown : sourceData; + let entityName = ''; + + // @ts-expect-error - we just check if the value is an entity + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + if (value && value.__identifier__ && value.__identifier__() === 'Entity') { + // @ts-expect-error - we know that the value is an entity + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call + entityName = value.getEntityName(); + } + + // @ts-expect-error - we just check if the value is an entityCollection + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + if (value && value.__identifier__ && value.__identifier__() === 'EntityCollection') { + // @ts-expect-error - we know that the value is an entityCollection + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call + entityName = value.entity; + } + + if (!entityName) { + return; + } + + const permissionsToCheck = ['read'] as const; + + permissionsToCheck.forEach((privilege) => { + const permissionsForPrivilege = extension?.permissions[privilege]; + + if ( + !permissionsForPrivilege || + !permissionsForPrivilege.includes(entityName) + ) { + permissionErrors.push(`${privilege}:${entityName}`); + } + }); + }); + + const value = get(sourceData, selector) as unknown; + + if (value !== undefined) { + acc[selector] = value; + } + + return acc; + }, {}); + + if (permissionErrors.length) { + return new MissingPrivilegesError(messageType, permissionErrors); + } + + return selectedData; +} diff --git a/src/data/composables/useSharedState.spec.ts b/src/data/composables/useSharedState.spec.ts index ab1099df..921f124f 100644 --- a/src/data/composables/useSharedState.spec.ts +++ b/src/data/composables/useSharedState.spec.ts @@ -1,4 +1,4 @@ -import { useSharedState } from './useSharedState'; +import type { useSharedState as useSharedStateType } from './useSharedState'; import { BroadcastChannel } from 'worker_threads'; import Vue from 'vue'; import flushPromises from 'flush-promises'; @@ -7,6 +7,8 @@ import localforage from 'localforage'; Vue.config.devtools = false; Vue.config.productionTip = false; +let useSharedState: typeof useSharedStateType; + function mockLoadComposableInApp(composable: () => any) { let result: any; @@ -29,6 +31,29 @@ describe('useSharedState composable', () => { storeName: 'persistentSharedValueStore', }); + beforeAll(async () => { + window.addEventListener('message', (event: MessageEvent) => { + if (event.origin === '') { + event.stopImmediatePropagation(); + const eventWithOrigin: MessageEvent = new MessageEvent('message', { + data: event.data, + origin: window.location.href, + }); + window.dispatchEvent(eventWithOrigin); + } + }); + + useSharedState = await (await import('./useSharedState')).useSharedState; + const setExtensions = await (await import('../../channel')).setExtensions; + + setExtensions({ + 'test-extension': { + baseUrl: 'http://localhost', + permissions: {}, + }, + }); + }) + beforeEach(async () => { // @ts-expect-error - Mocking BroadcastChannel global.BroadcastChannel = BroadcastChannel; diff --git a/src/data/index.ts b/src/data/index.ts index 76fc5b28..6c6757ca 100644 --- a/src/data/index.ts +++ b/src/data/index.ts @@ -1,4 +1,4 @@ -import { createHandler, createSender, subscribe as createSubscriber } from '../channel'; +import { createHandler, createSender, processDataRegistration, send, subscribe as createSubscriber } from '../channel'; import Criteria from './Criteria'; import Entity from './_internals/Entity'; import EntityCollection from './_internals/EntityCollection'; @@ -6,16 +6,39 @@ import repository from './repository'; // Internal function to create a filterable subscriber function createFilteredSubscriber(type: 'datasetSubscribe' | 'datasetUpdate') { - return (id: string, callback: (data: {id: string, data: unknown}) => void | Promise): unknown => { - const wrapper = (data: {id: string, data: unknown}): void => { - if (data && data.id === id) { - const returnValue = callback(data); - - if (returnValue) { - // eslint-disable-next-line @typescript-eslint/no-empty-function - returnValue.catch(() => {}); + return ( + id: string, + callback: (data: {id: string, data: unknown}) => void | Promise, + options?: { + selectors?: string[], + } + ): unknown => { + if (type === 'datasetSubscribe') { + // Send message to admin that this window wants to subscribe to a dataset + void send('datasetSubscribeRegistration', { + id, + selectors: options?.selectors, + }); + } + + const wrapper = (data: {id: string, data: unknown, selectors?: string[]}): void => { + if (data?.id !== id) { + return; + } + + if (data.selectors && data.selectors.length > 0) { + // Compare if the selectors match independent of the order + if (options?.selectors?.sort().join(',') !== data.selectors.sort().join(',')) { + return; } } + + const returnValue = callback(data); + + if (returnValue) { + // eslint-disable-next-line @typescript-eslint/no-empty-function + returnValue.catch(() => {}); + } }; return createSubscriber(type, wrapper as (data: unknown) => void | Promise); @@ -32,7 +55,7 @@ export const update = createSender('datasetUpdate'); /** * Internal methods used by the administration */ -export const register = createSender('datasetRegistration'); +export const register = processDataRegistration; export const updateSubscriber = createFilteredSubscriber('datasetUpdate'); export const handleGet = createHandler('datasetGet'); @@ -48,12 +71,31 @@ export type datasetRegistration = { data: unknown, } +/* + * TODO: how to handle multiple data subscribtion + * of the same id with different selectors? + */ + export type datasetSubscribe = { responseType: unknown, id: string, data: unknown, + + selectors?: string[], +} + +/** + * Will be used for giving the admin the information that + * a window wants to subscribe to a dataset + */ +export type datasetSubscribeRegistration = { + responseType: unknown, + + id: string, + + selectors?: string[], } export type datasetUpdate = { @@ -70,6 +112,8 @@ export type datasetGet = { id: string, data?: unknown, + + selectors?: string[], } const Classes: { diff --git a/src/messages.types.ts b/src/messages.types.ts index 5ee8d38d..c84c2b9b 100644 --- a/src/messages.types.ts +++ b/src/messages.types.ts @@ -14,7 +14,7 @@ import type { uiModalOpen, uiModalClose } from './ui/modal/index'; import type { actionButtonAdd } from './ui/actionButton'; import type { actionExecute } from './app/action'; import type Criteria from './data/Criteria'; -import type { datasetRegistration, datasetUpdate, datasetGet, datasetSubscribe } from './data'; +import type { datasetRegistration, datasetUpdate, datasetGet, datasetSubscribe, datasetSubscribeRegistration } from './data'; import type EntityCollection from './data/_internals/EntityCollection'; import type { Entity } from './data/_internals/Entity'; import type { @@ -72,6 +72,7 @@ export interface ShopwareMessageTypes { /* eslint-enable @typescript-eslint/no-explicit-any */ datasetRegistration: datasetRegistration, datasetSubscribe: datasetSubscribe, + datasetSubscribeRegistration: datasetSubscribeRegistration, datasetUpdate: datasetUpdate, datasetGet: datasetGet, __function__: __function__, @@ -153,4 +154,6 @@ export type __function__ = { export type __registerWindow__ = { responseType: void, + + sdkVersion: string, } diff --git a/src/privileges/privilege-resolver.ts b/src/privileges/privilege-resolver.ts index 9feb4434..b2bc9f52 100644 --- a/src/privileges/privilege-resolver.ts +++ b/src/privileges/privilege-resolver.ts @@ -73,6 +73,16 @@ function getMissingPrivileges(requiredPrivileges: privileges, privileges: privil } export function findExtensionByBaseUrl(baseUrl: string): extension | undefined { + if (typeof baseUrl !== 'string') { + return undefined; + } + + const comparedBaseUrl = new URL(baseUrl); + return Object.values(adminExtensions) - .find((ext) => ext.baseUrl === baseUrl); + .find((ext) => { + const extensionBaseUrl = new URL(ext.baseUrl); + + return extensionBaseUrl.hostname === comparedBaseUrl.hostname; + }); } diff --git a/tsconfig.json b/tsconfig.json index 42476402..297acf3b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,8 @@ "noUnusedParameters": true, "noImplicitReturns": true, "outDir": "./lib", - "declaration": true + "declaration": true, + "allowJs": true }, "include": ["src"] } diff --git a/vite.config.ts b/vite.config.ts index 3a2a305b..e12b703b 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -9,7 +9,7 @@ module.exports = defineConfig(({ command, mode }) => { return { root: resolve(__dirname, './e2e/testpage'), plugins: [ - tsconfigPaths() + tsconfigPaths(), ], build: { outDir: resolve(__dirname, 'testpageDist'),