1 import { MINUTE } from '@proton/shared/lib/constants';
2 import { getHostname } from '@proton/shared/lib/helpers/url';
4 export class ExternalSSOError extends Error {}
6 export const handleExternalSSOLogin = ({
13 finalRedirectBaseUrl?: string;
16 throw new Error('Unexpected response');
19 const url = new URL(`${window.location.origin}/api/auth/sso/${token}`);
21 if (finalRedirectBaseUrl) {
22 url.searchParams.set('FinalRedirectBaseUrl', finalRedirectBaseUrl);
25 const handleMessage = (event: MessageEvent) => {
26 if (event.data.action === 'sso' && event.data.payload) {
27 const uid: string = event.data.payload.uid;
28 const token: string = event.data.payload.token;
30 action: 'resolve' as const,
31 payload: { uid, token },
36 const tab = window.open(url);
39 throw new ExternalSSOError('Unable to open tab');
42 return new Promise<{ uid: string; token: string }>((resolve, reject) => {
43 let openHandle: ReturnType<typeof setInterval> | undefined = undefined;
44 let timeoutHandle: ReturnType<typeof setTimeout> | undefined = undefined;
45 let reset: () => void;
47 const assertOpen = () => {
48 if (!tab || tab.closed) {
50 reject(new ExternalSSOError('Process closed'));
54 const onMessage = (event: MessageEvent) => {
55 if (event.source !== tab && getHostname(event.origin) !== window.location.origin) {
59 const result = handleMessage(event);
64 if (result.action === 'resolve') {
65 resolve(result.payload);
66 } else if (result.action === 'reject') {
67 reject(result.payload);
77 reject(new ExternalSSOError('Process aborted'));
81 clearTimeout(timeoutHandle);
82 clearInterval(openHandle);
83 window.removeEventListener('message', onMessage, false);
84 signal.removeEventListener('abort', abort);
87 signal.addEventListener('abort', abort);
88 window.addEventListener('message', onMessage, false);
89 openHandle = setInterval(() => {
92 timeoutHandle = setTimeout(() => {