1 import { getAppHref } from '../apps/helper';
2 import { APPS } from '../constants';
3 import { getIsIframe } from '../helpers/browser';
4 import { isElectronMail } from '../helpers/desktop';
6 IPCInboxClientUpdateMessage,
7 IPCInboxDesktopFeature,
8 IPCInboxGetInfoMessage,
9 IPCInboxGetUserInfoMessage,
10 IPCInboxHostUpdateListener,
11 IPCInboxHostUpdateListenerRemover,
12 IPCInboxHostUpdateMessageType,
13 IPCInboxMessageBroker,
14 PayloadOfHostUpdateType,
15 } from './desktopTypes';
19 ipcInboxMessageBroker?: IPCInboxMessageBroker;
23 const IPC_POST_MESSAGE_NAME = 'InboxDesktopIPC' as const;
25 export function invokeInboxDesktopIPC({ type, payload }: IPCInboxClientUpdateMessage) {
26 if (!isElectronMail) {
27 return Promise.resolve();
30 if (window.ipcInboxMessageBroker?.send) {
31 window.ipcInboxMessageBroker!.send!(type, payload);
32 } else if (getIsIframe() && window.top) {
33 const messageId = crypto.randomUUID();
35 const messageReceptionPromise = new Promise<void>((resolve, reject) => {
36 let timeoutErrorId: ReturnType<typeof setTimeout>;
38 const handleMessage = (event: MessageEvent) => {
39 if (event.data?.name === IPC_POST_MESSAGE_NAME && event.data?.id === messageId) {
40 clearTimeout(timeoutErrorId);
41 window.removeEventListener('message', handleMessage);
46 timeoutErrorId = setTimeout(() => {
47 reject(new Error('IPC message was not received'));
48 window.removeEventListener('message', handleMessage);
51 window.addEventListener('message', handleMessage);
54 // With this targetOrigin resolution, we can only send IPC messages from frames
55 // to the mail application. Currently this is ok because we only need this from
56 // calendar drawer and mail previews, but might need improvement in the future.
57 const targetURL = new URL(getAppHref('/', APPS.PROTONMAIL, undefined, location));
59 window.top.postMessage(
60 { name: IPC_POST_MESSAGE_NAME, id: messageId, type, payload },
61 { targetOrigin: targetURL.origin }
64 return messageReceptionPromise;
67 return Promise.resolve();
70 export function handleInboxDesktopIPCPostMessages() {
71 window.addEventListener('message', (event) => {
72 if (event.data?.name === IPC_POST_MESSAGE_NAME) {
73 event.source?.postMessage(
74 { name: IPC_POST_MESSAGE_NAME, id: event.data.id },
75 { targetOrigin: event.origin }
77 void invokeInboxDesktopIPC({ type: event.data.type, payload: event.data.payload });
82 export const canGetInboxDesktopInfo =
83 isElectronMail && !!window.ipcInboxMessageBroker && !!window.ipcInboxMessageBroker!.getInfo;
85 export const canListenInboxDesktopHostMessages =
86 isElectronMail && !!window.ipcInboxMessageBroker && !!window.ipcInboxMessageBroker!.on;
88 export function getInboxDesktopInfo<T extends IPCInboxGetInfoMessage['type']>(key: T) {
89 return window.ipcInboxMessageBroker!.getInfo!<T>(key);
92 export const getInboxDesktopUserInfo = <T extends IPCInboxGetUserInfoMessage['type']>(key: T, userID: string) => {
93 return window.ipcInboxMessageBroker!.getUserInfo!<T>(key, userID);
96 export const hasInboxDesktopFeature = (feature: IPCInboxDesktopFeature) =>
98 !!window.ipcInboxMessageBroker &&
99 !!window.ipcInboxMessageBroker!.hasFeature &&
100 window.ipcInboxMessageBroker!.hasFeature(feature);
102 export const emptyListener: IPCInboxHostUpdateListenerRemover = { removeListener: () => {} };
104 export function addIPCHostUpdateListener<T extends IPCInboxHostUpdateMessageType>(
106 callback: (payload: PayloadOfHostUpdateType<T>) => void
107 ): IPCInboxHostUpdateListenerRemover {
108 // THIS NEEDS REFACTOR inda-refactor-001
109 // This shouldn't be needed, better to avoid it with custom type-safe event emmiter
111 // With generic T we make sure first correct callback type is added to
112 // correct event type. But the `on` function must accept union of callbacks.
113 const unsafeCallback = callback as IPCInboxHostUpdateListener;
114 return window.ipcInboxMessageBroker!.on!(eventType, unsafeCallback);