PageSourceSearch

https://ssamflow.com/js/panel-bridge.js

js ssamflow.com collected 2026-10-01 15:23:10 UTC 6,129 bytes, 148 lines download raw bytes

1// Chrome Side Panel ↔ SsamFlow page bridge.
2// The panel never receives the refresh token. It receives only the current
3// access token from the extension and uses it for short-lived panel requests.
4(function () {
5    const params = new URLSearchParams(location.search);
6    const panelMode = params.get('sf_panel') === '1' && window.parent !== window;
7    window.SF_PANEL_MODE = panelMode;
8    window.SF_PANEL_ACCESS_TOKEN = '';
9    document.documentElement.classList.toggle('sf-panel-mode', panelMode);
10
11    function send(message) {
12        if (!panelMode || window.parent === window) return;
13        window.parent.postMessage(message, '*');
14    }
15
16    function publishToken(token) {
17        window.SF_PANEL_ACCESS_TOKEN = typeof token === 'string' ? token : '';
18        window.dispatchEvent(new CustomEvent('sf-panel-auth', {
19            detail: { accessToken: window.SF_PANEL_ACCESS_TOKEN },
20        }));
21    }
22
23    async function acceptPanelAuth(token) {
24        if (!token) {
25            publishToken('');
26            window.SF_AUTH?.setPanelAuth?.(null);
27            return;
28        }
29        const response = await fetch('/api/panel/me', {
30            headers: { Authorization: 'Bearer ' + token },
31            cache: 'no-store',
32        }).catch(() => null);
33        if (!response?.ok) {
34            publishToken('');
35            window.SF_AUTH?.setPanelAuth?.(null);
36            return;
37        }
38        const body = await response.json().catch(() => null);
39        if (!body?.user) return;
40        publishToken(token);
41        window.SF_AUTH?.setPanelAuth?.({
42            access_token: token,
43            user: body.user,
44            is_panel: true,
45        });
46    }
47
48    // A side-panel login has an access token supplied by the extension, not a
49    // browser Supabase session. Before opening a protected sibling program,
50    // exchange that already-verified token for the shared SsamFlow cookie.
51    // This makes panel and web logins follow the same program handoff route.
52    async function syncPanelSession() {
53        // Side-panel mode receives a short-lived token from the extension.
54        // Normal SsamFlow pages already have a browser Supabase session, so
55        // use that session here as well instead of letting sibling programs
56        // perform a second, unauthenticated handoff.
57        let token = window.SF_PANEL_ACCESS_TOKEN;
58        let refreshToken = '';
59        let expiresIn = 3600;
60        if (!token && window.supabase?.auth?.getSession) {
61            try {
62                const result = await window.supabase.auth.getSession();
63                const session = result?.data?.session;
64                token = session?.access_token || '';
65                refreshToken = session?.refresh_token || '';
66                expiresIn = Number(session?.expires_in || 3600);
67            } catch {
68                token = '';
69            }
70        }
71        if (!token) return false;
72        try {
73            const response = await fetch('/api/auth/session', {
74                method: 'POST',
75                credentials: 'include',
76                headers: { 'Content-Type': 'application/json' },
77                body: JSON.stringify({
78                    access_token: token,
79                    refresh_token: refreshToken,
80                    expires_in: expiresIn,
81                }),
82            });
83            if (!response.ok) return false;
84            const verified = await fetch(`/api/auth/me?check=${encodeURIComponent(String(Date.now()))}`, { credentials: 'include', cache: 'no-store', headers: { 'Cache-Control': 'no-cache' } });
85            const identity = verified.ok ? await verified.json().catch(() => null) : null;
86            return Boolean(identity?.authenticated);
87        } catch {
88            return false;
89        }
90    }
91
92    window.sfOpenProgram = async function (url) {
93        const value = String(url || '');
94        try {
95            const target = new URL(value, location.origin);
96            if (!['http:', 'https:'].includes(target.protocol)) return;
97            const synced = await syncPanelSession();
98            // Keep the handoff explicit when a page was opened before the
99            // shared session finished syncing. This prevents a sibling app
100            // from redirecting back into its own gate over and over.
101            let nextUrl = target.toString();
102            if (!synced && target.hostname.endsWith('.ssamflow.com') && target.hostname !== 'ssamflow.com') {
103                const bridge = new URL('https://ssamflow.com/sso-bridge.html');
104                bridge.searchParams.set('returnTo', nextUrl);
105                nextUrl = bridge.toString();
106            }
107            if (panelMode) {
108                send({ type: 'SSAMFLOW_OPEN_PROGRAM', url: nextUrl });
109            } else {
110                window.open(nextUrl, '_blank', 'noopener');
111            }
112        } catch {
113            /* invalid program URL */
114        }
115    };
116
117    window.sfRequestPanelContext = function () {
118        send({ type: 'SSAMFLOW_REQUEST_CONTEXT' });
119    };
120
121    document.addEventListener('click', event => {
122        if (!panelMode) return;
123        const link = event.target.closest?.('a#sfMypageBtn');
124        if (!link) return;
125        event.preventDefault();
126        try {
127            send({ type: 'SSAMFLOW_OPEN_PROGRAM', url: new URL(link.href, location.origin).toString() });
128        } catch { /* invalid link */ }
129    });
130
131    window.addEventListener('message', event => {
132        if (!panelMode || event.source !== window.parent) return;
133        const message = event.data || {};
134        if (message.type === 'SSAMFLOW_PANEL_AUTH') {
135            acceptPanelAuth(message.accessToken);
136        }
137        if (message.type === 'SSAMFLOW_TAB_CONTEXT') {
138            window.dispatchEvent(new CustomEvent('sf-panel-context', {
139                detail: { url: typeof message.url === 'string' ? message.url : '' },
140            }));
141        }
142    });
143
144    window.addEventListener('DOMContentLoaded', () => {
145        send({ type: 'SSAMFLOW_PANEL_READY' });
146        if (window.SF_PANEL_ACCESS_TOKEN) acceptPanelAuth(window.SF_PANEL_ACCESS_TOKEN);
147    });
148})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.