Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add mobile context communication #196

Merged
merged 2 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/withContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const defaultContextValue: LocalContext = {
itemId: '',
memberId: '',
settings: {},
mobile: false,
dev: false,
offline: false,
lang: 'en',
Expand Down
102 changes: 87 additions & 15 deletions src/hooks/postMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const buildContext = (payload: LocalContext): LocalContext => {
lang = DEFAULT_LANG,
offline = false,
dev = false,
mobile = false,
settings = {},
} = payload;

Expand All @@ -38,14 +39,74 @@ export const buildContext = (payload: LocalContext): LocalContext => {
lang,
offline,
dev,
mobile,
standalone,
settings,
};
};

class CommunicationChannel {
isMobile: boolean;

channel: null | ((data: string) => void) = null;

port2: MessagePort | null = null;
spaenleh marked this conversation as resolved.
Show resolved Hide resolved

messageHandler: ((evt: MessageEvent) => void) | null = null;

constructor(
args:
| { isMobile: false; port2: MessagePort }
| { isMobile: true; handler?: (evt: MessageEvent) => void },
) {
this.isMobile = args.isMobile;
if (args.isMobile) {
if (args.handler) {
this.addHandler(args.handler);
}
} else if (args.port2) {
// when we are not on react native we use port communication
this.port2 = args.port2;
this.channel = args.port2.postMessage;
}
}

/**
* Function to send Data from the app to the Parent
* @param data Data to be sent to the parent
*/
postMessage(data: unknown): void {
if (this.isMobile) {
window.parent.postMessage(JSON.stringify(data));
} else {
this.port2?.postMessage(JSON.stringify(data));
}
}

addHandler(handler: (evt: MessageEvent) => void): void {
this.messageHandler = handler;
if (this.isMobile) {
window.addEventListener('message', this.messageHandler);
} else if (this.port2) {
this.port2.onmessage = handler;
}
}

removeHandler() {
if (this.messageHandler) {
window.removeEventListener('message', this.messageHandler);
}
}

useHandler(handler: (evt: MessageEvent) => void): void {
this.removeHandler();
this.addHandler(handler);
}
}

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
const configurePostMessageHooks = (queryConfig: QueryClientConfig) => {
let port2: MessagePort;
let communicationChannel: CommunicationChannel | null = null;

const postMessage: WindowPostMessage = (data: unknown) => {
console.debug('[app-postMessage] sending:', data);
Expand Down Expand Up @@ -98,6 +159,10 @@ const configurePostMessageHooks = (queryConfig: QueryClientConfig) => {
reject(`the type '${type}' for payload '${JSON.stringify(payload)}' is not recognized`);
}
} catch (e) {
queryConfig.notifier({
type: 'Resolution/rejection',
spaenleh marked this conversation as resolved.
Show resolved Hide resolved
payload: { message: (e as Error).message },
});
reject('an error occurred');
}
};
Expand Down Expand Up @@ -133,9 +198,14 @@ const configurePostMessageHooks = (queryConfig: QueryClientConfig) => {
// get init message getting the Message Channel port
const context = buildContext(payload);

// will use port for further communication
// set as a global variable
[port2] = event.ports;
if (context.mobile) {
communicationChannel = new CommunicationChannel({ isMobile: true });
} else {
communicationChannel = new CommunicationChannel({
isMobile: false,
port2: event.ports[0],
});
}
return context;
};

Expand All @@ -159,7 +229,7 @@ const configurePostMessageHooks = (queryConfig: QueryClientConfig) => {
});
},
onError: (error: Error) => {
queryConfig?.notifier?.({
queryConfig.notifier({
type: getLocalContextRoutine.FAILURE,
payload: { error },
});
Expand Down Expand Up @@ -188,8 +258,12 @@ const configurePostMessageHooks = (queryConfig: QueryClientConfig) => {
throw new Error('there was an error getting the query data for the LocalContext');
}
const POST_MESSAGE_KEYS = buildPostMessageKeys(itemId);
if (!port2) {
if (!communicationChannel) {
const error = new MissingMessageChannelPortError();
queryConfig.notifier({
type: 'error',
payload: { message: 'No communication Channel available' },
});
console.error(error);
throw error;
}
Expand All @@ -206,13 +280,11 @@ const configurePostMessageHooks = (queryConfig: QueryClientConfig) => {
(data: { payload: { token: string } }): string => data.payload.token,
);

port2.onmessage = getAuthTokenFunction;
port2.postMessage(
JSON.stringify({
type: POST_MESSAGE_KEYS.GET_AUTH_TOKEN,
payload: postMessagePayload,
}),
);
communicationChannel?.useHandler(getAuthTokenFunction);
communicationChannel?.postMessage({
type: POST_MESSAGE_KEYS.GET_AUTH_TOKEN,
payload: postMessagePayload,
});
});
},
onError: (error: Error) => {
Expand All @@ -230,14 +302,14 @@ const configurePostMessageHooks = (queryConfig: QueryClientConfig) => {
useEffect(() => {
if (!queryConfig.isStandalone) {
const sendHeight = (height: number): void => {
port2.postMessage(
communicationChannel?.postMessage(
JSON.stringify({
type: POST_MESSAGE_KEYS.POST_AUTO_RESIZE,
payload: height,
}),
);
};
if (!port2) {
if (!communicationChannel) {
const error = new MissingMessageChannelPortError();
console.error(error);
}
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export class UndefinedArgument extends Error {
export type WindowPostMessage = (message: unknown) => void;

export type LocalContext = {
mobile?: boolean;
apiHost: string;
itemId: UUID;
memberId: UUID;
Expand Down
1 change: 1 addition & 0 deletions test/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export const buildMockLocalContext = ({
itemId,
memberId,
settings: {},
mobile: false,
context: Context.Builder,
permission: PermissionLevel.Read,
});
Expand Down