@atlasauth/js 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +188 -0
- package/dist/api-keys.d.ts +84 -0
- package/dist/api-keys.js +77 -0
- package/dist/api-keys.js.map +1 -0
- package/dist/captcha-controller.d.ts +95 -0
- package/dist/captcha-controller.js +205 -0
- package/dist/captcha-controller.js.map +1 -0
- package/dist/captcha-widget.d.ts +81 -0
- package/dist/captcha-widget.js +263 -0
- package/dist/captcha-widget.js.map +1 -0
- package/dist/config.d.ts +59 -0
- package/dist/config.js +146 -0
- package/dist/config.js.map +1 -0
- package/dist/frontchannel-logout.d.ts +7 -0
- package/dist/frontchannel-logout.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/instance-settings.d.ts +58 -0
- package/dist/instance-settings.js +74 -0
- package/dist/instance-settings.js.map +1 -0
- package/dist/popup-channel.d.ts +95 -0
- package/dist/popup-channel.js +231 -0
- package/dist/popup-channel.js.map +1 -0
- package/dist/redirect.d.ts +53 -4
- package/dist/redirect.js +114 -9
- package/dist/redirect.js.map +1 -1
- package/dist/sign-out.d.ts +115 -0
- package/dist/sign-out.js +297 -0
- package/dist/sign-out.js.map +1 -0
- package/package.json +3 -2
package/dist/redirect.js
CHANGED
|
@@ -15,12 +15,14 @@
|
|
|
15
15
|
* the React provider and any custom integration read the exact same contract.
|
|
16
16
|
*/
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.REDIRECT_PARAMS = void 0;
|
|
18
|
+
exports.POPUP_SEVERED_TIMEOUT_MS = exports.POPUP_SEVERED_WINDOW_MS = exports.REDIRECT_PARAMS = void 0;
|
|
19
19
|
exports.startOauthRedirect = startOauthRedirect;
|
|
20
20
|
exports.parseOAuthPopupMessage = parseOAuthPopupMessage;
|
|
21
|
+
exports.mapOAuthPopupPayload = mapOAuthPopupPayload;
|
|
21
22
|
exports.startOauthPopup = startOauthPopup;
|
|
22
23
|
exports.readRedirectResult = readRedirectResult;
|
|
23
24
|
exports.stripRedirectParams = stripRedirectParams;
|
|
25
|
+
const popup_channel_1 = require("./popup-channel");
|
|
24
26
|
exports.REDIRECT_PARAMS = ['__atlas_attempt', '__atlas_ticket', '__atlas_status'];
|
|
25
27
|
/**
|
|
26
28
|
* Start a redirect-based OAuth sign-in for a provider that has no in-page native
|
|
@@ -48,6 +50,14 @@ async function startOauthRedirect(client, input) {
|
|
|
48
50
|
function parseOAuthPopupMessage(eventOrigin, expectedOrigin, data) {
|
|
49
51
|
if (eventOrigin !== expectedOrigin)
|
|
50
52
|
return null;
|
|
53
|
+
return mapOAuthPopupPayload(data);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Pure: map one of the Frontend API's `atlas:oauth:*` result payloads (from a
|
|
57
|
+
* `postMessage`, or relayed from a severed popup's landing page) to an outcome,
|
|
58
|
+
* or null when it is not one.
|
|
59
|
+
*/
|
|
60
|
+
function mapOAuthPopupPayload(data) {
|
|
51
61
|
if (!data || typeof data !== 'object')
|
|
52
62
|
return null;
|
|
53
63
|
const d = data;
|
|
@@ -67,6 +77,12 @@ function parseOAuthPopupMessage(eventOrigin, expectedOrigin, data) {
|
|
|
67
77
|
return null;
|
|
68
78
|
}
|
|
69
79
|
}
|
|
80
|
+
/** `popup.closed` this soon after opening means the opener link was severed, not a user close. */
|
|
81
|
+
exports.POPUP_SEVERED_WINDOW_MS = 1_500;
|
|
82
|
+
/** How long a severed popup is waited for before it counts as cancelled. */
|
|
83
|
+
exports.POPUP_SEVERED_TIMEOUT_MS = 5 * 60_000;
|
|
84
|
+
/** Back-off between session probes while a severed popup is pending. */
|
|
85
|
+
const SEVERED_PROBE_DELAYS_MS = [2_000, 3_000, 5_000, 8_000, 13_000, 21_000, 30_000];
|
|
70
86
|
function centeredPopupFeatures(w, h) {
|
|
71
87
|
const dualLeft = (window.screenLeft ?? window.screenX ?? 0);
|
|
72
88
|
const dualTop = (window.screenTop ?? window.screenY ?? 0);
|
|
@@ -76,18 +92,35 @@ function centeredPopupFeatures(w, h) {
|
|
|
76
92
|
const top = Math.max(0, dualTop + (vh - h) / 2);
|
|
77
93
|
return `popup,width=${w},height=${h},left=${Math.round(left)},top=${Math.round(top)}`;
|
|
78
94
|
}
|
|
95
|
+
/** The popup flow currently waiting in this page; a new one supersedes it. */
|
|
96
|
+
let cancelActivePopup = null;
|
|
79
97
|
/**
|
|
80
98
|
* Start a POPUP OAuth sign-in (GSI-style). Opens the provider authorize URL in a
|
|
81
99
|
* popup and resolves when the callback posts back the one-time ticket (complete),
|
|
82
100
|
* an error, or a continuation. Falls back to a full redirect when the popup is
|
|
83
101
|
* blocked — resolves `{ blocked:true, authorizationUrl }` and the caller
|
|
84
102
|
* navigates there, so it is always safe to enable. Browser-only.
|
|
103
|
+
*
|
|
104
|
+
* The result arrives by `postMessage` from the Frontend API, or — when a
|
|
105
|
+
* Cross-Origin-Opener-Policy severed the popup from this page — relayed over
|
|
106
|
+
* `BroadcastChannel('atlas:oauth:<nonce>')` by the SDK on the page the popup
|
|
107
|
+
* lands on (see ./popup-channel). Cancel semantics:
|
|
108
|
+
* - the popup closes after the first {@link POPUP_SEVERED_WINDOW_MS}: the user
|
|
109
|
+
* closed it — one final session probe, then `popup_closed`;
|
|
110
|
+
* - `popup.closed` reads true within that window: the link was severed (the
|
|
111
|
+
* popup is very much open). Keep waiting for the relayed result, probing
|
|
112
|
+
* the session with back-off, and settle as `popup_closed` only after
|
|
113
|
+
* {@link POPUP_SEVERED_TIMEOUT_MS}, an explicit error/cancel result,
|
|
114
|
+
* `input.signal` aborting, or a newer popup flow starting.
|
|
85
115
|
*/
|
|
86
116
|
async function startOauthPopup(client, input, handlers = {}) {
|
|
117
|
+
const nonce = (0, popup_channel_1.createPopupNonce)();
|
|
87
118
|
const res = await client.post('/v1/client/sign_ins/oauth', {
|
|
88
119
|
provider: input.provider,
|
|
89
120
|
redirect_url: input.redirectUrl,
|
|
90
121
|
mode: 'popup',
|
|
122
|
+
popup_nonce: nonce,
|
|
123
|
+
opener_origin: window.location.origin,
|
|
91
124
|
});
|
|
92
125
|
const authorizationUrl = res.ok ? res.data?.authorization_url : undefined;
|
|
93
126
|
if (!authorizationUrl)
|
|
@@ -95,7 +128,7 @@ async function startOauthPopup(client, input, handlers = {}) {
|
|
|
95
128
|
const expectedOrigin = client.frontendApiOrigin || window.location.origin;
|
|
96
129
|
let popup = null;
|
|
97
130
|
try {
|
|
98
|
-
popup = window.open(authorizationUrl,
|
|
131
|
+
popup = window.open(authorizationUrl, popup_channel_1.OAUTH_POPUP_WINDOW_NAME, centeredPopupFeatures(500, 650));
|
|
99
132
|
}
|
|
100
133
|
catch {
|
|
101
134
|
popup = null;
|
|
@@ -104,15 +137,28 @@ async function startOauthPopup(client, input, handlers = {}) {
|
|
|
104
137
|
handlers.onBlockedFallback?.(authorizationUrl);
|
|
105
138
|
return { blocked: true, authorizationUrl };
|
|
106
139
|
}
|
|
140
|
+
const openedAt = Date.now();
|
|
107
141
|
handlers.onOpen?.();
|
|
142
|
+
// Only one popup flow at a time: starting another cancels the one waiting.
|
|
143
|
+
cancelActivePopup?.();
|
|
108
144
|
return await new Promise((resolve) => {
|
|
109
145
|
let done = false;
|
|
146
|
+
let severed = false;
|
|
147
|
+
let probeTimer;
|
|
148
|
+
let deadline;
|
|
149
|
+
const cancel = () => settle({ error: 'popup_closed' });
|
|
110
150
|
const settle = (r) => {
|
|
111
151
|
if (done)
|
|
112
152
|
return;
|
|
113
153
|
done = true;
|
|
114
154
|
window.removeEventListener('message', onMessage);
|
|
115
155
|
clearInterval(timer);
|
|
156
|
+
clearTimeout(probeTimer);
|
|
157
|
+
clearTimeout(deadline);
|
|
158
|
+
stopListening();
|
|
159
|
+
input.signal?.removeEventListener('abort', cancel);
|
|
160
|
+
if (cancelActivePopup === cancel)
|
|
161
|
+
cancelActivePopup = null;
|
|
116
162
|
try {
|
|
117
163
|
if (popup && !popup.closed)
|
|
118
164
|
popup.close();
|
|
@@ -120,11 +166,10 @@ async function startOauthPopup(client, input, handlers = {}) {
|
|
|
120
166
|
catch {
|
|
121
167
|
/* cross-origin close can throw; ignore */
|
|
122
168
|
}
|
|
123
|
-
resolve(r);
|
|
169
|
+
resolve(severed ? { ...r, severed: true } : r);
|
|
124
170
|
};
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
if (!outcome)
|
|
171
|
+
const deliver = (outcome) => {
|
|
172
|
+
if (done)
|
|
128
173
|
return;
|
|
129
174
|
if (outcome.ticket)
|
|
130
175
|
handlers.onComplete?.({ ticket: outcome.ticket, attemptId: outcome.attemptId });
|
|
@@ -132,14 +177,74 @@ async function startOauthPopup(client, input, handlers = {}) {
|
|
|
132
177
|
handlers.onError?.(outcome.error);
|
|
133
178
|
settle(outcome);
|
|
134
179
|
};
|
|
180
|
+
const onMessage = (ev) => {
|
|
181
|
+
const outcome = parseOAuthPopupMessage(ev.origin, expectedOrigin, ev.data);
|
|
182
|
+
if (outcome)
|
|
183
|
+
deliver(outcome);
|
|
184
|
+
};
|
|
185
|
+
// A probe that finds a session settles; otherwise `onMiss` decides.
|
|
186
|
+
const probe = (onMiss) => {
|
|
187
|
+
if (done)
|
|
188
|
+
return;
|
|
189
|
+
void probeSession(client).then((signedIn) => {
|
|
190
|
+
if (done)
|
|
191
|
+
return;
|
|
192
|
+
if (signedIn)
|
|
193
|
+
settle({ error: 'popup_closed', sessionDetected: true });
|
|
194
|
+
else
|
|
195
|
+
onMiss();
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
const scheduleProbe = (attempt) => {
|
|
199
|
+
const delay = SEVERED_PROBE_DELAYS_MS[Math.min(attempt, SEVERED_PROBE_DELAYS_MS.length - 1)];
|
|
200
|
+
probeTimer = setTimeout(() => probe(() => scheduleProbe(attempt + 1)), delay);
|
|
201
|
+
};
|
|
202
|
+
const stopListening = (0, popup_channel_1.listenForPopupResult)(nonce, {
|
|
203
|
+
onResult: (payload) => {
|
|
204
|
+
const outcome = mapOAuthPopupPayload(payload);
|
|
205
|
+
if (outcome)
|
|
206
|
+
deliver(outcome);
|
|
207
|
+
},
|
|
208
|
+
// A page in the popup finished the sign-in itself: check now.
|
|
209
|
+
onSignedIn: () => probe(() => undefined),
|
|
210
|
+
});
|
|
135
211
|
// The user closing the popup without finishing is a cancellation, not a hang.
|
|
212
|
+
// But `popup.closed` turning true right after opening is not a close: the
|
|
213
|
+
// opener link was severed (Cross-Origin-Opener-Policy) and the popup is
|
|
214
|
+
// still on screen. Then wait for the relayed result instead.
|
|
136
215
|
const timer = setInterval(() => {
|
|
137
|
-
if (popup
|
|
138
|
-
|
|
139
|
-
|
|
216
|
+
if (!popup || !popup.closed || done)
|
|
217
|
+
return;
|
|
218
|
+
clearInterval(timer);
|
|
219
|
+
if (Date.now() - openedAt <= exports.POPUP_SEVERED_WINDOW_MS) {
|
|
220
|
+
severed = true;
|
|
221
|
+
handlers.onSevered?.();
|
|
222
|
+
deadline = setTimeout(cancel, exports.POPUP_SEVERED_TIMEOUT_MS);
|
|
223
|
+
scheduleProbe(0);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
// A real close. It can still follow a sign-in the popup finished on its
|
|
227
|
+
// own (an opener the browser severed later, mid-flow), so check the
|
|
228
|
+
// session once before calling it a cancel.
|
|
229
|
+
probe(cancel);
|
|
230
|
+
}, 250);
|
|
140
231
|
window.addEventListener('message', onMessage);
|
|
232
|
+
input.signal?.addEventListener('abort', cancel);
|
|
233
|
+
if (input.signal?.aborted)
|
|
234
|
+
cancel();
|
|
235
|
+
cancelActivePopup = cancel;
|
|
141
236
|
});
|
|
142
237
|
}
|
|
238
|
+
/** One `GET /v1/client`: is there a signed-in session on this browser now? */
|
|
239
|
+
async function probeSession(client) {
|
|
240
|
+
try {
|
|
241
|
+
const res = await client.get('/v1/client');
|
|
242
|
+
return Boolean(res.ok && res.data?.session && res.data?.user);
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
143
248
|
/**
|
|
144
249
|
* Read Atlas's redirect params from a query string (`window.location.search`).
|
|
145
250
|
*
|
package/dist/redirect.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redirect.js","sourceRoot":"","sources":["../src/redirect.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;;
|
|
1
|
+
{"version":3,"file":"redirect.js","sourceRoot":"","sources":["../src/redirect.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;;AAmBH,gDAUC;AAiCD,wDAOC;AAOD,oDAkBC;AAsFD,0CA+GC;AAmBD,gDAcC;AAQD,kDAIC;AA7UD,mDAIyB;AAEZ,QAAA,eAAe,GAAG,CAAC,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,CAAU,CAAC;AAEhG;;;;;;;GAOG;AACI,KAAK,UAAU,kBAAkB,CACtC,MAAkB,EAClB,KAAgD;IAEhD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAiC,2BAA2B,EAAE;QACzF,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,YAAY,EAAE,KAAK,CAAC,WAAW;KAChC,CAAC,CAAC;IACH,MAAM,GAAG,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC;IAC7D,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAChD,CAAC;AA0BD;;;;;;GAMG;AACH,SAAgB,sBAAsB,CACpC,WAAmB,EACnB,cAAsB,EACtB,IAAa;IAEb,IAAI,WAAW,KAAK,cAAc;QAAE,OAAO,IAAI,CAAC;IAChD,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC;AACpC,CAAC;AAED;;;;GAIG;AACH,SAAgB,oBAAoB,CAAC,IAAa;IAChD,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACnD,MAAM,CAAC,GAAG,IAA+B,CAAC;IAC1C,MAAM,GAAG,GAAG,CAAC,CAAU,EAAsB,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACxF,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QACf,KAAK,sBAAsB;YACzB,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC;QAChE,KAAK,mBAAmB;YACtB,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,OAAO,EAAE,eAAe,EAAE,GAAG,CAAC,CAAC,CAAC,eAAe,CAAC,EAAE,CAAC;QACpF,KAAK,sBAAsB;YACzB,OAAO;gBACL,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;gBACrB,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;gBAC3B,eAAe,EAAE,GAAG,CAAC,CAAC,CAAC,eAAe,CAAC;aACxC,CAAC;QACJ;YACE,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC;AA+CD,kGAAkG;AACrF,QAAA,uBAAuB,GAAG,KAAK,CAAC;AAC7C,4EAA4E;AAC/D,QAAA,wBAAwB,GAAG,CAAC,GAAG,MAAM,CAAC;AACnD,wEAAwE;AACxE,MAAM,uBAAuB,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAErF,SAAS,qBAAqB,CAAC,CAAS,EAAE,CAAS;IACjD,MAAM,QAAQ,GAAG,CAAC,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,OAAO,IAAI,CAAC,CAAW,CAAC;IACtE,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,OAAO,IAAI,CAAC,CAAW,CAAC;IACpE,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,IAAI,QAAQ,CAAC,eAAe,CAAC,WAAW,IAAI,CAAC,CAAC;IAC1E,MAAM,EAAE,GAAG,MAAM,CAAC,WAAW,IAAI,QAAQ,CAAC,eAAe,CAAC,YAAY,IAAI,CAAC,CAAC;IAC5E,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAClD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAChD,OAAO,eAAe,CAAC,WAAW,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;AACxF,CAAC;AAED,8EAA8E;AAC9E,IAAI,iBAAiB,GAAwB,IAAI,CAAC;AAElD;;;;;;;;;;;;;;;;;;GAkBG;AACI,KAAK,UAAU,eAAe,CACnC,MAAkB,EAClB,KAAsB,EACtB,WAA+B,EAAE;IAEjC,MAAM,KAAK,GAAG,IAAA,gCAAgB,GAAE,CAAC;IACjC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAiC,2BAA2B,EAAE;QACzF,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,YAAY,EAAE,KAAK,CAAC,WAAW;QAC/B,IAAI,EAAE,OAAO;QACb,WAAW,EAAE,KAAK;QAClB,aAAa,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM;KACtC,CAAC,CAAC;IACH,MAAM,gBAAgB,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1E,IAAI,CAAC,gBAAgB;QAAE,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC;IAExD,MAAM,cAAc,GAAG,MAAM,CAAC,iBAAiB,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;IAE1E,IAAI,KAAK,GAAkB,IAAI,CAAC;IAChC,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,uCAAuB,EAAE,qBAAqB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;IAClG,CAAC;IAAC,MAAM,CAAC;QACP,KAAK,GAAG,IAAI,CAAC;IACf,CAAC;IACD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,QAAQ,CAAC,iBAAiB,EAAE,CAAC,gBAAgB,CAAC,CAAC;QAC/C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC;IAC7C,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;IACpB,2EAA2E;IAC3E,iBAAiB,EAAE,EAAE,CAAC;IAEtB,OAAO,MAAM,IAAI,OAAO,CAAmB,CAAC,OAAO,EAAE,EAAE;QACrD,IAAI,IAAI,GAAG,KAAK,CAAC;QACjB,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,IAAI,UAAqD,CAAC;QAC1D,IAAI,QAAmD,CAAC;QACxD,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,CAAC,CAAmB,EAAE,EAAE;YACrC,IAAI,IAAI;gBAAE,OAAO;YACjB,IAAI,GAAG,IAAI,CAAC;YACZ,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;YACjD,aAAa,CAAC,KAAK,CAAC,CAAC;YACrB,YAAY,CAAC,UAAU,CAAC,CAAC;YACzB,YAAY,CAAC,QAAQ,CAAC,CAAC;YACvB,aAAa,EAAE,CAAC;YAChB,KAAK,CAAC,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YACnD,IAAI,iBAAiB,KAAK,MAAM;gBAAE,iBAAiB,GAAG,IAAI,CAAC;YAC3D,IAAI,CAAC;gBACH,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM;oBAAE,KAAK,CAAC,KAAK,EAAE,CAAC;YAC5C,CAAC;YAAC,MAAM,CAAC;gBACP,0CAA0C;YAC5C,CAAC;YACD,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjD,CAAC,CAAC;QACF,MAAM,OAAO,GAAG,CAAC,OAAqB,EAAE,EAAE;YACxC,IAAI,IAAI;gBAAE,OAAO;YACjB,IAAI,OAAO,CAAC,MAAM;gBAAE,QAAQ,CAAC,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;YACpG,IAAI,OAAO,CAAC,KAAK;gBAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACrD,MAAM,CAAC,OAAO,CAAC,CAAC;QAClB,CAAC,CAAC;QACF,MAAM,SAAS,GAAG,CAAC,EAAgB,EAAE,EAAE;YACrC,MAAM,OAAO,GAAG,sBAAsB,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;YAC3E,IAAI,OAAO;gBAAE,OAAO,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC,CAAC;QACF,oEAAoE;QACpE,MAAM,KAAK,GAAG,CAAC,MAAkB,EAAE,EAAE;YACnC,IAAI,IAAI;gBAAE,OAAO;YACjB,KAAK,YAAY,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;gBAC1C,IAAI,IAAI;oBAAE,OAAO;gBACjB,IAAI,QAAQ;oBAAE,MAAM,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC,CAAC;;oBAClE,MAAM,EAAE,CAAC;YAChB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;QACF,MAAM,aAAa,GAAG,CAAC,OAAe,EAAE,EAAE;YACxC,MAAM,KAAK,GAAG,uBAAuB,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,uBAAuB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAE,CAAC;YAC9F,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QAChF,CAAC,CAAC;QACF,MAAM,aAAa,GAAG,IAAA,oCAAoB,EAAC,KAAK,EAAE;YAChD,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE;gBACpB,MAAM,OAAO,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;gBAC9C,IAAI,OAAO;oBAAE,OAAO,CAAC,OAAO,CAAC,CAAC;YAChC,CAAC;YACD,8DAA8D;YAC9D,UAAU,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;SACzC,CAAC,CAAC;QACH,8EAA8E;QAC9E,0EAA0E;QAC1E,wEAAwE;QACxE,6DAA6D;QAC7D,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;YAC7B,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,IAAI;gBAAE,OAAO;YAC5C,aAAa,CAAC,KAAK,CAAC,CAAC;YACrB,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,IAAI,+BAAuB,EAAE,CAAC;gBACrD,OAAO,GAAG,IAAI,CAAC;gBACf,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC;gBACvB,QAAQ,GAAG,UAAU,CAAC,MAAM,EAAE,gCAAwB,CAAC,CAAC;gBACxD,aAAa,CAAC,CAAC,CAAC,CAAC;gBACjB,OAAO;YACT,CAAC;YACD,wEAAwE;YACxE,oEAAoE;YACpE,2CAA2C;YAC3C,KAAK,CAAC,MAAM,CAAC,CAAC;QAChB,CAAC,EAAE,GAAG,CAAC,CAAC;QACR,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QAC9C,KAAK,CAAC,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAChD,IAAI,KAAK,CAAC,MAAM,EAAE,OAAO;YAAE,MAAM,EAAE,CAAC;QACpC,iBAAiB,GAAG,MAAM,CAAC;IAC7B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,8EAA8E;AAC9E,KAAK,UAAU,YAAY,CAAC,MAAkB;IAC5C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,GAAG,CAAwC,YAAY,CAAC,CAAC;QAClF,OAAO,OAAO,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,IAAI,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,kBAAkB,CAAC,MAAc;IAC/C,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC;IAC3C,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAChD,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAE5B,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC5C,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEpC,OAAO;QACL,SAAS;QACT,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7B,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC9B,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAgB,mBAAmB,CAAC,IAAY;IAC9C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;IAC1B,KAAK,MAAM,GAAG,IAAI,uBAAe;QAAE,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAChE,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sign-out that cannot be silently undone.
|
|
3
|
+
*
|
|
4
|
+
* A browser sign-out has two halves: revoke the session on the Frontend API
|
|
5
|
+
* (which also clears the HttpOnly session cookies), and drop the local state.
|
|
6
|
+
* If the first half fails — a network blip, a 5xx — the refresh cookie is still
|
|
7
|
+
* valid, and the very next `reload()` (a tab regaining focus, a sibling tab's
|
|
8
|
+
* broadcast, a failed token refresh) re-boots straight back into the session
|
|
9
|
+
* the user just signed out of.
|
|
10
|
+
*
|
|
11
|
+
* These helpers close that gap: {@link revokeSession} retries once and reports
|
|
12
|
+
* what the server did (including the parked session it promoted, if any), and
|
|
13
|
+
* {@link SessionTombstones} remembers the ids of sessions this browser signed
|
|
14
|
+
* out of so a reload — or a tab opened later — never re-adopts one, even when
|
|
15
|
+
* the revoke never reached the server.
|
|
16
|
+
*/
|
|
17
|
+
/** What `signOut()` resolves with. */
|
|
18
|
+
export interface SignOutResult {
|
|
19
|
+
/**
|
|
20
|
+
* Whether the Frontend API confirmed the session was revoked. `false` means
|
|
21
|
+
* the local sign-out happened but the server could not be reached (or
|
|
22
|
+
* refused); the session id is remembered so this browser never re-adopts it.
|
|
23
|
+
*/
|
|
24
|
+
revoked: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Set when another account signed in on this browser was promoted to active
|
|
27
|
+
* (multi-session). The SDK adopts it, so the user stays signed in as it.
|
|
28
|
+
*/
|
|
29
|
+
activeSessionId?: string;
|
|
30
|
+
}
|
|
31
|
+
/** The error `signOut({ strict: true })` rejects with when the revoke fails. */
|
|
32
|
+
export declare class SignOutError extends Error {
|
|
33
|
+
readonly code: "SIGN_OUT_REVOKE_FAILED";
|
|
34
|
+
/** The session that could not be revoked. */
|
|
35
|
+
readonly sessionId: string;
|
|
36
|
+
/** The last HTTP status from the Frontend API, or `0` for a network failure. */
|
|
37
|
+
readonly status: number;
|
|
38
|
+
constructor(sessionId: string, status: number);
|
|
39
|
+
}
|
|
40
|
+
/** The outcome of {@link revokeSession}. */
|
|
41
|
+
export interface RevokeSessionOutcome {
|
|
42
|
+
revoked: boolean;
|
|
43
|
+
/** The last HTTP status (`0` when the request never got a response). */
|
|
44
|
+
status: number;
|
|
45
|
+
/** The successful response, for front-channel logout. */
|
|
46
|
+
response?: Response;
|
|
47
|
+
/** A parked session the server promoted to active, if any. */
|
|
48
|
+
activeSessionId?: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* `POST /v1/client/sessions/:id/revoke`, retried once on a network failure or a
|
|
52
|
+
* 5xx/429. A 4xx other than 429 is not retried: the server understood and said
|
|
53
|
+
* no. A 404 counts as revoked — the session is already gone.
|
|
54
|
+
*
|
|
55
|
+
* `request` is the caller's credentialed FAPI fetch (cookies included), so the
|
|
56
|
+
* server can clear the session cookies on this browser.
|
|
57
|
+
*/
|
|
58
|
+
export declare function revokeSession(request: (path: string, init?: RequestInit) => Promise<Response>, sessionId: string, options?: {
|
|
59
|
+
retries?: number;
|
|
60
|
+
}): Promise<RevokeSessionOutcome>;
|
|
61
|
+
/** Options for {@link SessionTombstones}. */
|
|
62
|
+
export interface SessionTombstonesOptions {
|
|
63
|
+
/**
|
|
64
|
+
* The Frontend API URL or origin the sessions belong to. Tombstones are kept
|
|
65
|
+
* per Frontend API origin, so two Atlas instances used on one site never
|
|
66
|
+
* share (or clear) each other's.
|
|
67
|
+
*/
|
|
68
|
+
frontendApi?: string;
|
|
69
|
+
/** Override the storage key entirely (the Frontend API scope is then not appended). */
|
|
70
|
+
storageKey?: string;
|
|
71
|
+
/** Clock, for tests. */
|
|
72
|
+
now?: () => number;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The ids of sessions this browser signed out of, so a reload — or a tab opened
|
|
76
|
+
* later — never re-adopts one from a refresh cookie the revoke did not clear.
|
|
77
|
+
*
|
|
78
|
+
* Kept in `localStorage` (shared by every tab of the site and surviving a
|
|
79
|
+
* restart), at most 20 entries, each for 30 days, pruned whenever it is read,
|
|
80
|
+
* and keyed per Frontend API origin. Tabs see each other's changes on every
|
|
81
|
+
* read, and {@link SessionTombstones.subscribe} reports them as they happen
|
|
82
|
+
* (the `storage` event). `sessionStorage` is used only when `localStorage`
|
|
83
|
+
* throws (storage disabled, sandboxed frames); memory always backs both. A
|
|
84
|
+
* confirmed revoke may {@link SessionTombstones.remove} its tombstone. Never
|
|
85
|
+
* holds a token — a session id is not a credential.
|
|
86
|
+
*/
|
|
87
|
+
export declare class SessionTombstones {
|
|
88
|
+
private entries;
|
|
89
|
+
/** Ids no storage accepted: only memory remembers them, so a sync keeps them. */
|
|
90
|
+
private unsaved;
|
|
91
|
+
private readonly storageKey;
|
|
92
|
+
private readonly now;
|
|
93
|
+
constructor(options?: string | SessionTombstonesOptions);
|
|
94
|
+
has(sessionId: string | null | undefined): boolean;
|
|
95
|
+
add(...sessionIds: (string | null | undefined)[]): void;
|
|
96
|
+
/**
|
|
97
|
+
* Forget tombstones — called once a later revoke of the session is confirmed
|
|
98
|
+
* (the server has ended it, so there is nothing left to re-adopt).
|
|
99
|
+
*/
|
|
100
|
+
remove(...sessionIds: (string | null | undefined)[]): void;
|
|
101
|
+
list(): string[];
|
|
102
|
+
/**
|
|
103
|
+
* Call `listener` when another tab adds or removes a tombstone (the `storage`
|
|
104
|
+
* event). Returns the unsubscribe function; a no-op outside a browser.
|
|
105
|
+
*/
|
|
106
|
+
subscribe(listener: (ids: string[]) => void): () => void;
|
|
107
|
+
/**
|
|
108
|
+
* Re-read storage — the source of truth, so another tab's add or remove is
|
|
109
|
+
* seen here — keeping only what this tab could not store; then drop expired
|
|
110
|
+
* entries. With no readable storage, memory is all there is.
|
|
111
|
+
*/
|
|
112
|
+
private sync;
|
|
113
|
+
private prune;
|
|
114
|
+
private persist;
|
|
115
|
+
}
|
package/dist/sign-out.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Sign-out that cannot be silently undone.
|
|
4
|
+
*
|
|
5
|
+
* A browser sign-out has two halves: revoke the session on the Frontend API
|
|
6
|
+
* (which also clears the HttpOnly session cookies), and drop the local state.
|
|
7
|
+
* If the first half fails — a network blip, a 5xx — the refresh cookie is still
|
|
8
|
+
* valid, and the very next `reload()` (a tab regaining focus, a sibling tab's
|
|
9
|
+
* broadcast, a failed token refresh) re-boots straight back into the session
|
|
10
|
+
* the user just signed out of.
|
|
11
|
+
*
|
|
12
|
+
* These helpers close that gap: {@link revokeSession} retries once and reports
|
|
13
|
+
* what the server did (including the parked session it promoted, if any), and
|
|
14
|
+
* {@link SessionTombstones} remembers the ids of sessions this browser signed
|
|
15
|
+
* out of so a reload — or a tab opened later — never re-adopts one, even when
|
|
16
|
+
* the revoke never reached the server.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.SessionTombstones = exports.SignOutError = void 0;
|
|
20
|
+
exports.revokeSession = revokeSession;
|
|
21
|
+
/** The error `signOut({ strict: true })` rejects with when the revoke fails. */
|
|
22
|
+
class SignOutError extends Error {
|
|
23
|
+
code = 'SIGN_OUT_REVOKE_FAILED';
|
|
24
|
+
/** The session that could not be revoked. */
|
|
25
|
+
sessionId;
|
|
26
|
+
/** The last HTTP status from the Frontend API, or `0` for a network failure. */
|
|
27
|
+
status;
|
|
28
|
+
constructor(sessionId, status) {
|
|
29
|
+
super(status === 0
|
|
30
|
+
? 'Signed out locally, but the Atlas Frontend API could not be reached to end the session.'
|
|
31
|
+
: `Signed out locally, but the Atlas Frontend API refused to end the session (HTTP ${status}).`);
|
|
32
|
+
this.name = 'SignOutError';
|
|
33
|
+
this.sessionId = sessionId;
|
|
34
|
+
this.status = status;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.SignOutError = SignOutError;
|
|
38
|
+
/**
|
|
39
|
+
* `POST /v1/client/sessions/:id/revoke`, retried once on a network failure or a
|
|
40
|
+
* 5xx/429. A 4xx other than 429 is not retried: the server understood and said
|
|
41
|
+
* no. A 404 counts as revoked — the session is already gone.
|
|
42
|
+
*
|
|
43
|
+
* `request` is the caller's credentialed FAPI fetch (cookies included), so the
|
|
44
|
+
* server can clear the session cookies on this browser.
|
|
45
|
+
*/
|
|
46
|
+
async function revokeSession(request, sessionId, options = {}) {
|
|
47
|
+
const retries = Math.max(0, options.retries ?? 1);
|
|
48
|
+
let status = 0;
|
|
49
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
50
|
+
let response;
|
|
51
|
+
try {
|
|
52
|
+
response = await request(`/v1/client/sessions/${encodeURIComponent(sessionId)}/revoke`, {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
status = 0;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
status = response.status;
|
|
61
|
+
if (response.ok) {
|
|
62
|
+
let activeSessionId;
|
|
63
|
+
try {
|
|
64
|
+
const body = (await response.clone().json());
|
|
65
|
+
if (typeof body?.active_session_id === 'string')
|
|
66
|
+
activeSessionId = body.active_session_id;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// A body we cannot read still means the revoke succeeded.
|
|
70
|
+
}
|
|
71
|
+
return { revoked: true, status, response, ...(activeSessionId ? { activeSessionId } : {}) };
|
|
72
|
+
}
|
|
73
|
+
if (status === 404)
|
|
74
|
+
return { revoked: true, status };
|
|
75
|
+
if (status < 500 && status !== 429)
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
return { revoked: false, status };
|
|
79
|
+
}
|
|
80
|
+
const TOMBSTONE_KEY = 'atlas:signed-out-sessions';
|
|
81
|
+
const TOMBSTONE_LIMIT = 20;
|
|
82
|
+
/** How long a tombstone is kept: longer than any refresh cookie it guards against. */
|
|
83
|
+
const TOMBSTONE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
84
|
+
/**
|
|
85
|
+
* The ids of sessions this browser signed out of, so a reload — or a tab opened
|
|
86
|
+
* later — never re-adopts one from a refresh cookie the revoke did not clear.
|
|
87
|
+
*
|
|
88
|
+
* Kept in `localStorage` (shared by every tab of the site and surviving a
|
|
89
|
+
* restart), at most 20 entries, each for 30 days, pruned whenever it is read,
|
|
90
|
+
* and keyed per Frontend API origin. Tabs see each other's changes on every
|
|
91
|
+
* read, and {@link SessionTombstones.subscribe} reports them as they happen
|
|
92
|
+
* (the `storage` event). `sessionStorage` is used only when `localStorage`
|
|
93
|
+
* throws (storage disabled, sandboxed frames); memory always backs both. A
|
|
94
|
+
* confirmed revoke may {@link SessionTombstones.remove} its tombstone. Never
|
|
95
|
+
* holds a token — a session id is not a credential.
|
|
96
|
+
*/
|
|
97
|
+
class SessionTombstones {
|
|
98
|
+
entries;
|
|
99
|
+
/** Ids no storage accepted: only memory remembers them, so a sync keeps them. */
|
|
100
|
+
unsaved = new Set();
|
|
101
|
+
storageKey;
|
|
102
|
+
now;
|
|
103
|
+
constructor(options = {}) {
|
|
104
|
+
const opts = typeof options === 'string' ? { storageKey: options } : options;
|
|
105
|
+
this.storageKey = opts.storageKey ?? scopedKey(opts.frontendApi);
|
|
106
|
+
this.now = opts.now ?? Date.now;
|
|
107
|
+
const stored = readStored(this.storageKey) ?? [];
|
|
108
|
+
const legacy = readLegacy(this.storageKey, this.now()).filter((l) => !stored.some((e) => e.id === l.id));
|
|
109
|
+
this.entries = this.prune([...stored, ...legacy]);
|
|
110
|
+
// Carry an older SDK's per-tab tombstones over into the shared store, once.
|
|
111
|
+
if (legacy.length) {
|
|
112
|
+
this.persist();
|
|
113
|
+
try {
|
|
114
|
+
storage('session')?.removeItem(TOMBSTONE_KEY);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
/* sessionStorage unavailable */
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
has(sessionId) {
|
|
122
|
+
if (typeof sessionId !== 'string' || !sessionId)
|
|
123
|
+
return false;
|
|
124
|
+
this.sync();
|
|
125
|
+
return this.entries.some((e) => e.id === sessionId);
|
|
126
|
+
}
|
|
127
|
+
add(...sessionIds) {
|
|
128
|
+
this.sync();
|
|
129
|
+
let changed = false;
|
|
130
|
+
for (const id of sessionIds) {
|
|
131
|
+
if (typeof id !== 'string' || !id || this.entries.some((e) => e.id === id))
|
|
132
|
+
continue;
|
|
133
|
+
this.entries.push({ id, at: this.now() });
|
|
134
|
+
changed = true;
|
|
135
|
+
}
|
|
136
|
+
if (changed)
|
|
137
|
+
this.persist();
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Forget tombstones — called once a later revoke of the session is confirmed
|
|
141
|
+
* (the server has ended it, so there is nothing left to re-adopt).
|
|
142
|
+
*/
|
|
143
|
+
remove(...sessionIds) {
|
|
144
|
+
this.sync();
|
|
145
|
+
const before = this.entries.length;
|
|
146
|
+
this.entries = this.entries.filter((e) => !sessionIds.includes(e.id));
|
|
147
|
+
if (this.entries.length !== before)
|
|
148
|
+
this.persist();
|
|
149
|
+
}
|
|
150
|
+
list() {
|
|
151
|
+
this.sync();
|
|
152
|
+
return this.entries.map((e) => e.id);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Call `listener` when another tab adds or removes a tombstone (the `storage`
|
|
156
|
+
* event). Returns the unsubscribe function; a no-op outside a browser.
|
|
157
|
+
*/
|
|
158
|
+
subscribe(listener) {
|
|
159
|
+
const target = globalThis;
|
|
160
|
+
if (typeof target.addEventListener !== 'function')
|
|
161
|
+
return () => undefined;
|
|
162
|
+
const onStorage = (event) => {
|
|
163
|
+
if (event.key !== this.storageKey && event.key !== null)
|
|
164
|
+
return;
|
|
165
|
+
listener(this.list());
|
|
166
|
+
};
|
|
167
|
+
target.addEventListener('storage', onStorage);
|
|
168
|
+
return () => target.removeEventListener?.('storage', onStorage);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Re-read storage — the source of truth, so another tab's add or remove is
|
|
172
|
+
* seen here — keeping only what this tab could not store; then drop expired
|
|
173
|
+
* entries. With no readable storage, memory is all there is.
|
|
174
|
+
*/
|
|
175
|
+
sync() {
|
|
176
|
+
const stored = readStored(this.storageKey);
|
|
177
|
+
if (!stored) {
|
|
178
|
+
this.entries = this.prune(this.entries);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const known = new Set(stored.map((e) => e.id));
|
|
182
|
+
this.entries = this.prune([
|
|
183
|
+
...stored,
|
|
184
|
+
...this.entries.filter((e) => this.unsaved.has(e.id) && !known.has(e.id)),
|
|
185
|
+
]);
|
|
186
|
+
}
|
|
187
|
+
prune(entries) {
|
|
188
|
+
const cutoff = this.now() - TOMBSTONE_TTL_MS;
|
|
189
|
+
const seen = new Set();
|
|
190
|
+
const fresh = entries
|
|
191
|
+
.filter((e) => e.at > cutoff && !seen.has(e.id) && seen.add(e.id))
|
|
192
|
+
.sort((a, b) => a.at - b.at);
|
|
193
|
+
return fresh.slice(-TOMBSTONE_LIMIT);
|
|
194
|
+
}
|
|
195
|
+
persist() {
|
|
196
|
+
this.entries = this.prune(this.entries);
|
|
197
|
+
const raw = JSON.stringify(this.entries);
|
|
198
|
+
if (write('local', this.storageKey, raw)) {
|
|
199
|
+
// Anything parked in sessionStorage by an earlier failed write is now in localStorage.
|
|
200
|
+
try {
|
|
201
|
+
storage('session')?.removeItem(this.storageKey);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
/* nothing parked, or sessionStorage unavailable */
|
|
205
|
+
}
|
|
206
|
+
this.unsaved.clear();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
// localStorage threw (disabled, sandboxed, full): fall back to this tab's
|
|
210
|
+
// sessionStorage, and memory still protects this tab if that throws too.
|
|
211
|
+
this.unsaved = write('session', this.storageKey, raw) ? new Set() : new Set(this.entries.map((e) => e.id));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
exports.SessionTombstones = SessionTombstones;
|
|
215
|
+
/** `atlas:signed-out-sessions:<frontend api origin>`. */
|
|
216
|
+
function scopedKey(frontendApi) {
|
|
217
|
+
if (!frontendApi)
|
|
218
|
+
return TOMBSTONE_KEY;
|
|
219
|
+
try {
|
|
220
|
+
return `${TOMBSTONE_KEY}:${new URL(frontendApi).origin}`;
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return `${TOMBSTONE_KEY}:${frontendApi}`;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function storage(kind) {
|
|
227
|
+
// Reading `localStorage` itself throws in some sandboxed contexts.
|
|
228
|
+
return kind === 'local' ? globalThis.localStorage : globalThis.sessionStorage;
|
|
229
|
+
}
|
|
230
|
+
function write(kind, key, value) {
|
|
231
|
+
try {
|
|
232
|
+
const s = storage(kind);
|
|
233
|
+
if (!s)
|
|
234
|
+
return false;
|
|
235
|
+
s.setItem(key, value);
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function parseEntries(raw, now) {
|
|
243
|
+
if (!raw)
|
|
244
|
+
return [];
|
|
245
|
+
try {
|
|
246
|
+
const parsed = JSON.parse(raw);
|
|
247
|
+
if (!Array.isArray(parsed))
|
|
248
|
+
return [];
|
|
249
|
+
const out = [];
|
|
250
|
+
for (const v of parsed) {
|
|
251
|
+
// Bare ids are the pre-TTL format: date them now.
|
|
252
|
+
if (typeof v === 'string' && v)
|
|
253
|
+
out.push({ id: v, at: now });
|
|
254
|
+
else if (v && typeof v === 'object') {
|
|
255
|
+
const { id, at } = v;
|
|
256
|
+
if (typeof id === 'string' && id && typeof at === 'number' && Number.isFinite(at))
|
|
257
|
+
out.push({ id, at });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return out;
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* localStorage (plus anything a failed write parked in sessionStorage), or
|
|
268
|
+
* sessionStorage alone when localStorage throws; null when neither is readable.
|
|
269
|
+
*/
|
|
270
|
+
function readStored(key) {
|
|
271
|
+
const now = Date.now();
|
|
272
|
+
try {
|
|
273
|
+
const s = storage('local');
|
|
274
|
+
if (s)
|
|
275
|
+
return [...parseEntries(s.getItem(key), now), ...(readSession(key, now) ?? [])];
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
/* fall through to sessionStorage */
|
|
279
|
+
}
|
|
280
|
+
return readSession(key, now);
|
|
281
|
+
}
|
|
282
|
+
function readSession(key, now) {
|
|
283
|
+
try {
|
|
284
|
+
const s = storage('session');
|
|
285
|
+
return s ? parseEntries(s.getItem(key), now) : null;
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/** Tombstones written by earlier SDK versions: per tab, unscoped, in sessionStorage. */
|
|
292
|
+
function readLegacy(key, now) {
|
|
293
|
+
if (key === TOMBSTONE_KEY)
|
|
294
|
+
return [];
|
|
295
|
+
return readSession(TOMBSTONE_KEY, now) ?? [];
|
|
296
|
+
}
|
|
297
|
+
//# sourceMappingURL=sign-out.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign-out.js","sourceRoot":"","sources":["../src/sign-out.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;;AAwDH,sCAgCC;AAvED,gFAAgF;AAChF,MAAa,YAAa,SAAQ,KAAK;IAC5B,IAAI,GAAG,wBAAiC,CAAC;IAClD,6CAA6C;IACpC,SAAS,CAAS;IAC3B,gFAAgF;IACvE,MAAM,CAAS;IAExB,YAAY,SAAiB,EAAE,MAAc;QAC3C,KAAK,CACH,MAAM,KAAK,CAAC;YACV,CAAC,CAAC,yFAAyF;YAC3F,CAAC,CAAC,mFAAmF,MAAM,IAAI,CAClG,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;QAC3B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAjBD,oCAiBC;AAaD;;;;;;;GAOG;AACI,KAAK,UAAU,aAAa,CACjC,OAAgE,EAChE,SAAiB,EACjB,UAAgC,EAAE;IAElC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC;IAClD,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC;QACpD,IAAI,QAAkB,CAAC;QACvB,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,OAAO,CAAC,uBAAuB,kBAAkB,CAAC,SAAS,CAAC,SAAS,EAAE;gBACtF,MAAM,EAAE,MAAM;aACf,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,GAAG,CAAC,CAAC;YACX,SAAS;QACX,CAAC;QACD,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;QACzB,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;YAChB,IAAI,eAAmC,CAAC;YACxC,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAA2C,CAAC;gBACvF,IAAI,OAAO,IAAI,EAAE,iBAAiB,KAAK,QAAQ;oBAAE,eAAe,GAAG,IAAI,CAAC,iBAAiB,CAAC;YAC5F,CAAC;YAAC,MAAM,CAAC;gBACP,0DAA0D;YAC5D,CAAC;YACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAC9F,CAAC;QACD,IAAI,MAAM,KAAK,GAAG;YAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QACrD,IAAI,MAAM,GAAG,GAAG,IAAI,MAAM,KAAK,GAAG;YAAE,MAAM;IAC5C,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AACpC,CAAC;AAED,MAAM,aAAa,GAAG,2BAA2B,CAAC;AAClD,MAAM,eAAe,GAAG,EAAE,CAAC;AAC3B,sFAAsF;AACtF,MAAM,gBAAgB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAwBlD;;;;;;;;;;;;GAYG;AACH,MAAa,iBAAiB;IACpB,OAAO,CAAmB;IAClC,iFAAiF;IACzE,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IACnB,UAAU,CAAS;IACnB,GAAG,CAAe;IAEnC,YAAY,UAA6C,EAAE;QACzD,MAAM,IAAI,GAA6B,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;QACvG,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACjE,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;QAChC,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QACjD,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACzG,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC;QAClD,4EAA4E;QAC5E,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClB,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,IAAI,CAAC;gBACH,OAAO,CAAC,SAAS,CAAC,EAAE,UAAU,CAAC,aAAa,CAAC,CAAC;YAChD,CAAC;YAAC,MAAM,CAAC;gBACP,gCAAgC;YAClC,CAAC;QACH,CAAC;IACH,CAAC;IAED,GAAG,CAAC,SAAoC;QACtC,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,CAAC,SAAS;YAAE,OAAO,KAAK,CAAC;QAC9D,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC;IACtD,CAAC;IAED,GAAG,CAAC,GAAG,UAAyC;QAC9C,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,KAAK,MAAM,EAAE,IAAI,UAAU,EAAE,CAAC;YAC5B,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;gBAAE,SAAS;YACrF,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YAC1C,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;QACD,IAAI,OAAO;YAAE,IAAI,CAAC,OAAO,EAAE,CAAC;IAC9B,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,GAAG,UAAyC;QACjD,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;QACnC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACtE,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,MAAM;YAAE,IAAI,CAAC,OAAO,EAAE,CAAC;IACrD,CAAC;IAED,IAAI;QACF,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACvC,CAAC;IAED;;;OAGG;IACH,SAAS,CAAC,QAAiC;QACzC,MAAM,MAAM,GAAG,UAAoH,CAAC;QACpI,IAAI,OAAO,MAAM,CAAC,gBAAgB,KAAK,UAAU;YAAE,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC;QAC1E,MAAM,SAAS,GAAG,CAAC,KAAmB,EAAE,EAAE;YACxC,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI,CAAC,UAAU,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI;gBAAE,OAAO;YAChE,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACxB,CAAC,CAAC;QACF,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QAC9C,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAClE,CAAC;IAED;;;;OAIG;IACK,IAAI;QACV,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC3C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACxC,OAAO;QACT,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC/C,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC;YACxB,GAAG,MAAM;YACT,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;SAC1E,CAAC,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,OAAyB;QACrC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,gBAAgB,CAAC;QAC7C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,MAAM,KAAK,GAAG,OAAO;aAClB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;aACjE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/B,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,eAAe,CAAC,CAAC;IACvC,CAAC;IAEO,OAAO;QACb,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzC,IAAI,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,GAAG,CAAC,EAAE,CAAC;YACzC,uFAAuF;YACvF,IAAI,CAAC;gBACH,OAAO,CAAC,SAAS,CAAC,EAAE,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAClD,CAAC;YAAC,MAAM,CAAC;gBACP,mDAAmD;YACrD,CAAC;YACD,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACrB,OAAO;QACT,CAAC;QACD,0EAA0E;QAC1E,yEAAyE;QACzE,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7G,CAAC;CACF;AArHD,8CAqHC;AAED,yDAAyD;AACzD,SAAS,SAAS,CAAC,WAA+B;IAChD,IAAI,CAAC,WAAW;QAAE,OAAO,aAAa,CAAC;IACvC,IAAI,CAAC;QACH,OAAO,GAAG,aAAa,IAAI,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC,MAAM,EAAE,CAAC;IAC3D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,aAAa,IAAI,WAAW,EAAE,CAAC;IAC3C,CAAC;AACH,CAAC;AAED,SAAS,OAAO,CAAC,IAAiB;IAChC,mEAAmE;IACnE,OAAO,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC,cAAc,CAAC;AAChF,CAAC;AAED,SAAS,KAAK,CAAC,IAAiB,EAAE,GAAW,EAAE,KAAa;IAC1D,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QACxB,IAAI,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;QACrB,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,GAA8B,EAAE,GAAW;IAC/D,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,OAAO,EAAE,CAAC;QACtC,MAAM,GAAG,GAAqB,EAAE,CAAC;QACjC,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;YACvB,kDAAkD;YAClD,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;iBACxD,IAAI,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACpC,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,CAAmC,CAAC;gBACvD,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;YAC1G,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,UAAU,CAAC,GAAW;IAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QAC3B,IAAI,CAAC;YAAE,OAAO,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACzF,CAAC;IAAC,MAAM,CAAC;QACP,oCAAoC;IACtC,CAAC;IACD,OAAO,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AAC/B,CAAC;AAED,SAAS,WAAW,CAAC,GAAW,EAAE,GAAW;IAC3C,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;QAC7B,OAAO,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,wFAAwF;AACxF,SAAS,UAAU,CAAC,GAAW,EAAE,GAAW;IAC1C,IAAI,GAAG,KAAK,aAAa;QAAE,OAAO,EAAE,CAAC;IACrC,OAAO,WAAW,CAAC,aAAa,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC;AAC/C,CAAC"}
|