Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / packages / shared / lib / desktop / ipcHelpers.ts
blobe8b8e845274708825031068a128a5c9a91b5c114
1 import { getAppHref } from '../apps/helper';
2 import { APPS } from '../constants';
3 import { getIsIframe } from '../helpers/browser';
4 import { isElectronMail } from '../helpers/desktop';
5 import type {
6     IPCInboxClientUpdateMessage,
7     IPCInboxDesktopFeature,
8     IPCInboxGetInfoMessage,
9     IPCInboxGetUserInfoMessage,
10     IPCInboxHostUpdateListener,
11     IPCInboxHostUpdateListenerRemover,
12     IPCInboxHostUpdateMessageType,
13     IPCInboxMessageBroker,
14     PayloadOfHostUpdateType,
15 } from './desktopTypes';
17 declare global {
18     interface Window {
19         ipcInboxMessageBroker?: IPCInboxMessageBroker;
20     }
23 const IPC_POST_MESSAGE_NAME = 'InboxDesktopIPC' as const;
25 export function invokeInboxDesktopIPC({ type, payload }: IPCInboxClientUpdateMessage) {
26     if (!isElectronMail) {
27         return Promise.resolve();
28     }
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);
42                     resolve();
43                 }
44             };
46             timeoutErrorId = setTimeout(() => {
47                 reject(new Error('IPC message was not received'));
48                 window.removeEventListener('message', handleMessage);
49             }, 5000);
51             window.addEventListener('message', handleMessage);
52         });
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 }
62         );
64         return messageReceptionPromise;
65     }
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 }
76             );
77             void invokeInboxDesktopIPC({ type: event.data.type, payload: event.data.payload });
78         }
79     });
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) =>
97     isElectronMail &&
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>(
105     eventType: T,
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
110     //
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);