Skip to content
This repository has been archived by the owner on Mar 28, 2024. It is now read-only.

Commit

Permalink
NEXT-28243 - improve dataset handling with selector
Browse files Browse the repository at this point in the history
  • Loading branch information
jleifeld committed Jun 20, 2023
1 parent ebbe8ab commit 1a74cd0
Show file tree
Hide file tree
Showing 14 changed files with 360 additions and 55 deletions.
4 changes: 3 additions & 1 deletion e2e/channel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
})

Expand Down Expand Up @@ -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);
});

Expand Down
28 changes: 14 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/_internals/error-handling/error-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
7 changes: 7 additions & 0 deletions src/_internals/sdkVersion.js
Original file line number Diff line number Diff line change
@@ -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;
11 changes: 9 additions & 2 deletions src/_internals/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default function validate({
const extension = findExtensionByBaseUrl(origin);

if (!extension) {
console.warn(`No extension found for origin ${origin}`);
return null;
}

Expand All @@ -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}`);
}
Expand Down
47 changes: 46 additions & 1 deletion src/channel.spec.ts
Original file line number Diff line number Diff line change
@@ -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({});
Expand Down
127 changes: 105 additions & 22 deletions src/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
});
});
}

/**
Expand Down Expand Up @@ -64,6 +77,14 @@ export type ShopwareMessageResponseData<MESSAGE_TYPE extends keyof ShopwareMessa
const sourceRegistry: Set<{
source: Window,
origin: string,
sdkVersion: string|undefined,
}> = new Set();

const subscriberRegistry: Set<{
id: string,
selectors: string[] | undefined,
source: Window,
origin: string,
}> = new Set();

/**
Expand Down Expand Up @@ -367,10 +388,19 @@ export function handle<MESSAGE_TYPE extends keyof ShopwareMessageTypes>
export function publish<MESSAGE_TYPE extends keyof ShopwareMessageTypes>(
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(() => {});
Expand Down Expand Up @@ -421,7 +451,9 @@ export function createSender<MESSAGE_TYPE extends keyof ShopwareMessageTypes>
*/
export function createHandler<MESSAGE_TYPE extends keyof ShopwareMessageTypes>(messageType: MESSAGE_TYPE) {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
return (method: (data: MessageDataType<MESSAGE_TYPE>) => Promise<ShopwareMessageTypes[MESSAGE_TYPE]['responseType']> | ShopwareMessageTypes[MESSAGE_TYPE]['responseType']) => {
return (method: (data: MessageDataType<MESSAGE_TYPE>, additionalInformation: {
_event_: MessageEvent<string>,
}) => Promise<ShopwareMessageTypes[MESSAGE_TYPE]['responseType']> | ShopwareMessageTypes[MESSAGE_TYPE]['responseType']) => {
return handle(messageType, method);
};
}
Expand Down Expand Up @@ -457,7 +489,7 @@ const datasets = new Map<string, unknown>();

(async (): Promise<void> => {
// Handle registrations at current window
handle('__registerWindow__', (_, additionalOptions) => {
handle('__registerWindow__', ({ sdkVersion }, additionalOptions) => {
let source: Window | undefined;
let origin: string | undefined;

Expand All @@ -472,35 +504,82 @@ const datasets = new Map<string, unknown>();
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<datasetRegistration, 'responseType'>): Promise<void> {
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
Expand All @@ -509,14 +588,18 @@ const datasets = new Map<string, unknown>();
interface Window {
_swsdk: {
sourceRegistry: typeof sourceRegistry,
subscriberRegistry: typeof subscriberRegistry,
datasets: typeof datasets,
adminExtensions: typeof adminExtensions,
},
}
}

window._swsdk = {
sourceRegistry,
subscriberRegistry,
datasets,
adminExtensions,
};

/**
Expand Down
Loading

0 comments on commit 1a74cd0

Please sign in to comment.