@ghostly-solutions/auth 0.2.1 → 0.2.3
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/LICENSE +13 -0
- package/README.md +167 -28
- package/dist/extension.d.ts +40 -4
- package/dist/extension.js +374 -200
- package/dist/extension.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/next.js.map +1 -1
- package/dist/react.js.map +1 -1
- package/docs/api-reference.md +1 -0
- package/docs/integration-guide.md +17 -8
- package/docs/overview.md +5 -0
- package/extension.js +1 -0
- package/package.json +11 -3
package/dist/extension.js
CHANGED
|
@@ -2,11 +2,19 @@
|
|
|
2
2
|
var authApiPrefix = "/oauth";
|
|
3
3
|
var authEndpoints = {
|
|
4
4
|
authorize: `${authApiPrefix}/authorize`,
|
|
5
|
+
extensionToken: `${authApiPrefix}/extension/token`,
|
|
5
6
|
session: `${authApiPrefix}/session`,
|
|
6
7
|
refresh: `${authApiPrefix}/refresh`,
|
|
7
8
|
logout: `${authApiPrefix}/logout`
|
|
8
9
|
};
|
|
9
10
|
|
|
11
|
+
// src/constants/http-status.ts
|
|
12
|
+
var httpStatus = {
|
|
13
|
+
ok: 200,
|
|
14
|
+
noContent: 204,
|
|
15
|
+
unauthorized: 401
|
|
16
|
+
};
|
|
17
|
+
|
|
10
18
|
// src/errors/auth-sdk-error.ts
|
|
11
19
|
var AuthSdkError = class extends Error {
|
|
12
20
|
code;
|
|
@@ -68,13 +76,6 @@ function resolveApiEndpoint(path, apiOrigin) {
|
|
|
68
76
|
return `${normalizeApiOrigin(apiOrigin)}${path}`;
|
|
69
77
|
}
|
|
70
78
|
|
|
71
|
-
// src/constants/http-status.ts
|
|
72
|
-
var httpStatus = {
|
|
73
|
-
ok: 200,
|
|
74
|
-
noContent: 204,
|
|
75
|
-
unauthorized: 401
|
|
76
|
-
};
|
|
77
|
-
|
|
78
79
|
// src/constants/auth-keys.ts
|
|
79
80
|
var authBroadcast = {
|
|
80
81
|
channelName: "ghostly-auth-channel",
|
|
@@ -152,115 +153,6 @@ function createBroadcastSync(options) {
|
|
|
152
153
|
};
|
|
153
154
|
}
|
|
154
155
|
|
|
155
|
-
// src/core/http-client.ts
|
|
156
|
-
var jsonContentType = "application/json";
|
|
157
|
-
var jsonHeaderName = "content-type";
|
|
158
|
-
var includeCredentials = "include";
|
|
159
|
-
var noStoreCache = "no-store";
|
|
160
|
-
function toTypedValue(value) {
|
|
161
|
-
return value;
|
|
162
|
-
}
|
|
163
|
-
function mapHttpStatusToAuthErrorCode(status) {
|
|
164
|
-
if (status === httpStatus.unauthorized) {
|
|
165
|
-
return authErrorCode.unauthorized;
|
|
166
|
-
}
|
|
167
|
-
return authErrorCode.apiError;
|
|
168
|
-
}
|
|
169
|
-
async function parseJsonPayload(response) {
|
|
170
|
-
try {
|
|
171
|
-
return await response.json();
|
|
172
|
-
} catch {
|
|
173
|
-
return null;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
async function parseErrorPayload(response) {
|
|
177
|
-
const payload = await parseJsonPayload(response);
|
|
178
|
-
if (!isObjectRecord(payload)) {
|
|
179
|
-
return {
|
|
180
|
-
code: null,
|
|
181
|
-
details: null,
|
|
182
|
-
message: null
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
const maybeCode = payload.code;
|
|
186
|
-
const maybeMessage = payload.message;
|
|
187
|
-
return {
|
|
188
|
-
code: isStringValue(maybeCode) ? maybeCode : null,
|
|
189
|
-
details: "details" in payload ? payload.details : null,
|
|
190
|
-
message: isStringValue(maybeMessage) ? maybeMessage : null
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
function buildApiErrorMessage(method, path) {
|
|
194
|
-
return `Auth API request failed: ${method} ${path}`;
|
|
195
|
-
}
|
|
196
|
-
function buildNetworkErrorMessage(method, path) {
|
|
197
|
-
return `Auth API network failure: ${method} ${path}`;
|
|
198
|
-
}
|
|
199
|
-
async function request(options) {
|
|
200
|
-
const expectedStatus = options.expectedStatus ?? httpStatus.ok;
|
|
201
|
-
const headers = new Headers();
|
|
202
|
-
const hasBody = typeof options.body !== "undefined";
|
|
203
|
-
if (hasBody) {
|
|
204
|
-
headers.set(jsonHeaderName, jsonContentType);
|
|
205
|
-
}
|
|
206
|
-
const requestInit = {
|
|
207
|
-
cache: noStoreCache,
|
|
208
|
-
credentials: includeCredentials,
|
|
209
|
-
headers,
|
|
210
|
-
method: options.method
|
|
211
|
-
};
|
|
212
|
-
if (hasBody) {
|
|
213
|
-
requestInit.body = JSON.stringify(options.body);
|
|
214
|
-
}
|
|
215
|
-
let response;
|
|
216
|
-
try {
|
|
217
|
-
response = await fetch(options.path, requestInit);
|
|
218
|
-
} catch (error) {
|
|
219
|
-
throw new AuthSdkError({
|
|
220
|
-
code: authErrorCode.networkError,
|
|
221
|
-
details: error,
|
|
222
|
-
message: buildNetworkErrorMessage(options.method, options.path),
|
|
223
|
-
status: null
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
if (response.status !== expectedStatus) {
|
|
227
|
-
const parsed = await parseErrorPayload(response);
|
|
228
|
-
throw new AuthSdkError({
|
|
229
|
-
code: mapHttpStatusToAuthErrorCode(response.status),
|
|
230
|
-
details: {
|
|
231
|
-
apiCode: parsed.code,
|
|
232
|
-
apiDetails: parsed.details
|
|
233
|
-
},
|
|
234
|
-
message: parsed.message ?? buildApiErrorMessage(options.method, options.path),
|
|
235
|
-
status: response.status
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
if (response.status === httpStatus.noContent) {
|
|
239
|
-
return toTypedValue(null);
|
|
240
|
-
}
|
|
241
|
-
const payload = await parseJsonPayload(response);
|
|
242
|
-
return toTypedValue(payload);
|
|
243
|
-
}
|
|
244
|
-
function getJson(path) {
|
|
245
|
-
return request({
|
|
246
|
-
method: "GET",
|
|
247
|
-
path
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
function postJsonWithoutBody(path) {
|
|
251
|
-
return request({
|
|
252
|
-
method: "POST",
|
|
253
|
-
path
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
function postEmpty(path) {
|
|
257
|
-
return request({
|
|
258
|
-
expectedStatus: httpStatus.noContent,
|
|
259
|
-
method: "POST",
|
|
260
|
-
path
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
|
|
264
156
|
// src/core/runtime.ts
|
|
265
157
|
var browserRuntimeErrorMessage = "Browser runtime is required for this auth operation.";
|
|
266
158
|
function isBrowserRuntime() {
|
|
@@ -330,27 +222,11 @@ var SessionStore = class {
|
|
|
330
222
|
}
|
|
331
223
|
};
|
|
332
224
|
|
|
333
|
-
// src/
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
message: `Auth API response has invalid session shape: ${path}`,
|
|
339
|
-
status: null
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
function toValidatedSession(payload, path) {
|
|
343
|
-
if (!isGhostlySession(payload)) {
|
|
344
|
-
throw createInvalidSessionPayloadError(path);
|
|
345
|
-
}
|
|
346
|
-
return payload;
|
|
347
|
-
}
|
|
348
|
-
function toSessionPayload(payload, path) {
|
|
349
|
-
if (payload === null) {
|
|
350
|
-
return null;
|
|
351
|
-
}
|
|
352
|
-
return toValidatedSession(payload, path);
|
|
353
|
-
}
|
|
225
|
+
// src/adapters/extension/auth-client.ts
|
|
226
|
+
var extensionSessionHeader = "X-Ghostly-Session-Id";
|
|
227
|
+
var includeCredentials = "include";
|
|
228
|
+
var noStoreCache = "no-store";
|
|
229
|
+
var tokenFreshnessLeewayMs = 6e4;
|
|
354
230
|
function createNoopBroadcastSync() {
|
|
355
231
|
return {
|
|
356
232
|
close() {
|
|
@@ -371,25 +247,142 @@ function createSafeBroadcastSync(onSessionUpdated) {
|
|
|
371
247
|
throw error;
|
|
372
248
|
}
|
|
373
249
|
}
|
|
250
|
+
function buildApiErrorMessage(method, path) {
|
|
251
|
+
return `Auth API request failed: ${method} ${path}`;
|
|
252
|
+
}
|
|
253
|
+
function buildNetworkErrorMessage(method, path) {
|
|
254
|
+
return `Auth API network failure: ${method} ${path}`;
|
|
255
|
+
}
|
|
256
|
+
function createInvalidSessionPayloadError(path) {
|
|
257
|
+
return new AuthSdkError({
|
|
258
|
+
code: authErrorCode.apiError,
|
|
259
|
+
details: null,
|
|
260
|
+
message: `Auth API response has invalid session shape: ${path}`,
|
|
261
|
+
status: null
|
|
262
|
+
});
|
|
263
|
+
}
|
|
374
264
|
function toInitResult(session) {
|
|
375
265
|
return {
|
|
376
266
|
session,
|
|
377
267
|
status: session ? "authenticated" : "unauthenticated"
|
|
378
268
|
};
|
|
379
269
|
}
|
|
380
|
-
function
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
}
|
|
270
|
+
function parseSessionPayload(payload, path) {
|
|
271
|
+
if (payload === null) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
if (!isGhostlySession(payload)) {
|
|
275
|
+
throw createInvalidSessionPayloadError(path);
|
|
276
|
+
}
|
|
277
|
+
return payload;
|
|
278
|
+
}
|
|
279
|
+
function isExtensionAccessToken(value) {
|
|
280
|
+
if (!isObjectRecord(value)) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
return isStringValue(value.accessToken) && isStringValue(value.application) && (value.expiresAt === null || isStringValue(value.expiresAt)) && (value.session === null || isGhostlySession(value.session)) && isStringValue(value.tokenType);
|
|
284
|
+
}
|
|
285
|
+
function isStoredTokenFresh(token) {
|
|
286
|
+
if (!token || !token.accessToken.trim()) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
if (!token.expiresAt) {
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
const expiresAt = Date.parse(token.expiresAt);
|
|
293
|
+
if (Number.isNaN(expiresAt)) {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
return expiresAt - Date.now() > tokenFreshnessLeewayMs;
|
|
297
|
+
}
|
|
298
|
+
function buildAuthorizeUrl(apiOrigin, returnTo, application) {
|
|
299
|
+
const authorizeEndpoint = resolveApiEndpoint(authEndpoints.authorize, apiOrigin);
|
|
300
|
+
const authorizeUrl = new URL(authorizeEndpoint, window.location.origin);
|
|
301
|
+
authorizeUrl.searchParams.set("return_to", returnTo);
|
|
302
|
+
if (application?.trim()) {
|
|
303
|
+
authorizeUrl.searchParams.set("app", application.trim());
|
|
304
|
+
}
|
|
305
|
+
return authorizeUrl.toString();
|
|
306
|
+
}
|
|
307
|
+
function resolveExtensionReturnToPath(requestedReturnTo, defaultReturnToPath) {
|
|
308
|
+
if (requestedReturnTo?.trim()) {
|
|
309
|
+
return resolveReturnToPath(requestedReturnTo);
|
|
310
|
+
}
|
|
311
|
+
if (defaultReturnToPath?.trim()) {
|
|
312
|
+
return resolveReturnToPath(defaultReturnToPath);
|
|
313
|
+
}
|
|
314
|
+
return resolveReturnToPath(requestedReturnTo);
|
|
315
|
+
}
|
|
316
|
+
function parseErrorPayload(payload) {
|
|
317
|
+
if (!isObjectRecord(payload)) {
|
|
318
|
+
return {
|
|
319
|
+
details: null,
|
|
320
|
+
message: null
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
details: "details" in payload ? payload.details : null,
|
|
325
|
+
message: isStringValue(payload.message) ? payload.message : isStringValue(payload.error) ? payload.error : null
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function createRequest(options) {
|
|
387
329
|
const resolveEndpoint = (path) => resolveApiEndpoint(path, options.apiOrigin);
|
|
388
|
-
|
|
389
|
-
const
|
|
390
|
-
|
|
330
|
+
return async function request(requestOptions) {
|
|
331
|
+
const expectedStatus = requestOptions.expectedStatus ?? httpStatus.ok;
|
|
332
|
+
const headers = new Headers();
|
|
333
|
+
const sessionId = (await options.resolveSessionId?.())?.trim() || "";
|
|
334
|
+
if (sessionId) {
|
|
335
|
+
headers.set(extensionSessionHeader, sessionId);
|
|
336
|
+
}
|
|
337
|
+
let response;
|
|
338
|
+
try {
|
|
339
|
+
response = await fetch(resolveEndpoint(requestOptions.path), {
|
|
340
|
+
cache: noStoreCache,
|
|
341
|
+
credentials: includeCredentials,
|
|
342
|
+
headers,
|
|
343
|
+
method: requestOptions.method
|
|
344
|
+
});
|
|
345
|
+
} catch (error) {
|
|
346
|
+
throw new AuthSdkError({
|
|
347
|
+
code: authErrorCode.networkError,
|
|
348
|
+
details: error,
|
|
349
|
+
message: buildNetworkErrorMessage(requestOptions.method, requestOptions.path),
|
|
350
|
+
status: null
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
if (response.status !== expectedStatus) {
|
|
354
|
+
let parsedPayload = null;
|
|
355
|
+
try {
|
|
356
|
+
parsedPayload = await response.json();
|
|
357
|
+
} catch {
|
|
358
|
+
parsedPayload = null;
|
|
359
|
+
}
|
|
360
|
+
const parsed = parseErrorPayload(parsedPayload);
|
|
361
|
+
throw new AuthSdkError({
|
|
362
|
+
code: response.status === httpStatus.unauthorized ? authErrorCode.unauthorized : authErrorCode.apiError,
|
|
363
|
+
details: parsed.details,
|
|
364
|
+
message: parsed.message ?? buildApiErrorMessage(requestOptions.method, requestOptions.path),
|
|
365
|
+
status: response.status
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
if (response.status === httpStatus.noContent) {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
return await response.json();
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
function createLoadSession(request) {
|
|
375
|
+
return async () => {
|
|
376
|
+
const payload = await request({
|
|
377
|
+
method: "GET",
|
|
378
|
+
path: authEndpoints.session
|
|
379
|
+
});
|
|
380
|
+
return parseSessionPayload(payload, authEndpoints.session);
|
|
391
381
|
};
|
|
392
|
-
|
|
382
|
+
}
|
|
383
|
+
function createInit(sessionStore, loadSession, publishSession) {
|
|
384
|
+
let initPromise = null;
|
|
385
|
+
return async (initOptions) => {
|
|
393
386
|
const forceRefresh = initOptions?.forceRefresh ?? false;
|
|
394
387
|
if (sessionStore.hasResolvedSession() && !forceRefresh) {
|
|
395
388
|
return toInitResult(sessionStore.getSessionIfResolved());
|
|
@@ -400,7 +393,7 @@ function createAuthClient(options = {}) {
|
|
|
400
393
|
initPromise = (async () => {
|
|
401
394
|
const session = await loadSession();
|
|
402
395
|
sessionStore.setSession(session);
|
|
403
|
-
|
|
396
|
+
publishSession(session);
|
|
404
397
|
return toInitResult(session);
|
|
405
398
|
})();
|
|
406
399
|
try {
|
|
@@ -409,19 +402,91 @@ function createAuthClient(options = {}) {
|
|
|
409
402
|
initPromise = null;
|
|
410
403
|
}
|
|
411
404
|
};
|
|
405
|
+
}
|
|
406
|
+
function createRefresh(request, sessionStore, publishSession) {
|
|
407
|
+
return async () => {
|
|
408
|
+
const payload = await request({
|
|
409
|
+
method: "POST",
|
|
410
|
+
path: authEndpoints.refresh
|
|
411
|
+
});
|
|
412
|
+
const session = parseSessionPayload(payload, authEndpoints.refresh);
|
|
413
|
+
sessionStore.setSession(session);
|
|
414
|
+
publishSession(session);
|
|
415
|
+
return session;
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function createGetAccessToken(request, sessionStore, publishSession, persistAccessToken, restoreAccessToken) {
|
|
419
|
+
return async (requestOptions) => {
|
|
420
|
+
const forceRefresh = requestOptions?.forceRefresh ?? false;
|
|
421
|
+
const stored = await restoreAccessToken?.() ?? null;
|
|
422
|
+
if (!forceRefresh && isStoredTokenFresh(stored)) {
|
|
423
|
+
if (stored.session) {
|
|
424
|
+
sessionStore.setSession(stored.session);
|
|
425
|
+
}
|
|
426
|
+
return stored;
|
|
427
|
+
}
|
|
428
|
+
const payload = await request({
|
|
429
|
+
method: "GET",
|
|
430
|
+
path: authEndpoints.extensionToken
|
|
431
|
+
});
|
|
432
|
+
if (!isExtensionAccessToken(payload)) {
|
|
433
|
+
throw new AuthSdkError({
|
|
434
|
+
code: authErrorCode.apiError,
|
|
435
|
+
details: null,
|
|
436
|
+
message: `Auth API response has invalid extension token shape: ${authEndpoints.extensionToken}`,
|
|
437
|
+
status: null
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
const nextToken = {
|
|
441
|
+
accessToken: payload.accessToken,
|
|
442
|
+
application: payload.application,
|
|
443
|
+
expiresAt: payload.expiresAt,
|
|
444
|
+
session: payload.session,
|
|
445
|
+
tokenType: payload.tokenType
|
|
446
|
+
};
|
|
447
|
+
sessionStore.setSession(nextToken.session);
|
|
448
|
+
publishSession(nextToken.session);
|
|
449
|
+
await persistAccessToken?.(nextToken);
|
|
450
|
+
return nextToken;
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
function createLogout(request, sessionStore, publishSession, options) {
|
|
454
|
+
return async () => {
|
|
455
|
+
await request({
|
|
456
|
+
expectedStatus: httpStatus.noContent,
|
|
457
|
+
method: "POST",
|
|
458
|
+
path: authEndpoints.logout
|
|
459
|
+
});
|
|
460
|
+
await options.clearSessionId?.();
|
|
461
|
+
sessionStore.setSession(null);
|
|
462
|
+
publishSession(null);
|
|
463
|
+
await options.persistAccessToken?.(null);
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
function createCustomExtensionAuthClient(options) {
|
|
467
|
+
const sessionStore = new SessionStore();
|
|
468
|
+
const broadcastSync = createSafeBroadcastSync((session) => {
|
|
469
|
+
sessionStore.setSession(session);
|
|
470
|
+
});
|
|
471
|
+
const publishSession = broadcastSync.publishSession.bind(broadcastSync);
|
|
472
|
+
const request = createRequest(options);
|
|
473
|
+
const loadSession = createLoadSession(request);
|
|
474
|
+
const init = createInit(sessionStore, loadSession, publishSession);
|
|
475
|
+
const refresh = createRefresh(request, sessionStore, publishSession);
|
|
476
|
+
const getAccessToken = createGetAccessToken(
|
|
477
|
+
request,
|
|
478
|
+
sessionStore,
|
|
479
|
+
publishSession,
|
|
480
|
+
options.persistAccessToken,
|
|
481
|
+
options.restoreAccessToken
|
|
482
|
+
);
|
|
483
|
+
const logout = createLogout(request, sessionStore, publishSession, options);
|
|
412
484
|
const getSession = async (requestOptions) => {
|
|
413
485
|
const result = await init({
|
|
414
486
|
forceRefresh: requestOptions?.forceRefresh ?? false
|
|
415
487
|
});
|
|
416
488
|
return result.session;
|
|
417
489
|
};
|
|
418
|
-
const refresh = async () => {
|
|
419
|
-
const payload = await postJsonWithoutBody(resolveEndpoint(authEndpoints.refresh));
|
|
420
|
-
const session = toSessionPayload(payload, authEndpoints.refresh);
|
|
421
|
-
sessionStore.setSession(session);
|
|
422
|
-
broadcastSync.publishSession(session);
|
|
423
|
-
return session;
|
|
424
|
-
};
|
|
425
490
|
const requireSession = async () => {
|
|
426
491
|
const session = await getSession();
|
|
427
492
|
if (session) {
|
|
@@ -434,50 +499,31 @@ function createAuthClient(options = {}) {
|
|
|
434
499
|
status: httpStatus.unauthorized
|
|
435
500
|
});
|
|
436
501
|
};
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
authorizeUrl.searchParams.set("return_to", returnTo);
|
|
441
|
-
const application = loginOptions?.application?.trim() || defaultApplication;
|
|
442
|
-
if (application) {
|
|
443
|
-
authorizeUrl.searchParams.set("app", application);
|
|
502
|
+
const loginWithTabFlow = async (loginOptions) => {
|
|
503
|
+
if (!options.openAuthorizePage) {
|
|
504
|
+
throw new Error("Extension auth client requires openAuthorizePage for tab login flow.");
|
|
444
505
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
logout,
|
|
458
|
-
refresh,
|
|
459
|
-
requireSession,
|
|
460
|
-
subscribe
|
|
506
|
+
const returnTo = resolveExtensionReturnToPath(
|
|
507
|
+
loginOptions?.returnTo,
|
|
508
|
+
options.defaultReturnToPath
|
|
509
|
+
);
|
|
510
|
+
const authorizeUrl = buildAuthorizeUrl(
|
|
511
|
+
options.apiOrigin,
|
|
512
|
+
returnTo,
|
|
513
|
+
loginOptions?.application ?? options.application
|
|
514
|
+
);
|
|
515
|
+
await options.openAuthorizePage({
|
|
516
|
+
authorizeUrl
|
|
517
|
+
});
|
|
461
518
|
};
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// src/adapters/extension/auth-client.ts
|
|
465
|
-
function buildAuthorizeUrl(apiOrigin, returnTo, application) {
|
|
466
|
-
const authorizeEndpoint = resolveApiEndpoint(authEndpoints.authorize, apiOrigin);
|
|
467
|
-
const authorizeUrl = new URL(authorizeEndpoint, window.location.origin);
|
|
468
|
-
authorizeUrl.searchParams.set("return_to", returnTo);
|
|
469
|
-
if (application?.trim()) {
|
|
470
|
-
authorizeUrl.searchParams.set("app", application.trim());
|
|
471
|
-
}
|
|
472
|
-
return authorizeUrl.toString();
|
|
473
|
-
}
|
|
474
|
-
function createExtensionAuthClient(options) {
|
|
475
|
-
const baseClient = createAuthClient({
|
|
476
|
-
apiOrigin: options.apiOrigin,
|
|
477
|
-
application: options.application
|
|
478
|
-
});
|
|
479
519
|
const loginWithWebAuthFlow = async (loginOptions) => {
|
|
480
|
-
|
|
520
|
+
if (!options.launchWebAuthFlow) {
|
|
521
|
+
throw new Error("Extension auth client requires launchWebAuthFlow for web auth flow.");
|
|
522
|
+
}
|
|
523
|
+
const returnTo = resolveExtensionReturnToPath(
|
|
524
|
+
loginOptions?.returnTo,
|
|
525
|
+
options.defaultReturnToPath
|
|
526
|
+
);
|
|
481
527
|
const authorizeUrl = buildAuthorizeUrl(
|
|
482
528
|
options.apiOrigin,
|
|
483
529
|
returnTo,
|
|
@@ -486,17 +532,145 @@ function createExtensionAuthClient(options) {
|
|
|
486
532
|
await options.launchWebAuthFlow({
|
|
487
533
|
authorizeUrl
|
|
488
534
|
});
|
|
489
|
-
await
|
|
535
|
+
await refresh();
|
|
490
536
|
};
|
|
491
537
|
return {
|
|
492
|
-
|
|
538
|
+
init,
|
|
539
|
+
getSession,
|
|
493
540
|
login(loginOptions) {
|
|
541
|
+
if (options.openAuthorizePage) {
|
|
542
|
+
void loginWithTabFlow(loginOptions);
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
494
545
|
void loginWithWebAuthFlow(loginOptions);
|
|
495
546
|
},
|
|
547
|
+
logout,
|
|
548
|
+
refresh,
|
|
549
|
+
requireSession,
|
|
550
|
+
subscribe(listener) {
|
|
551
|
+
return sessionStore.subscribe(listener);
|
|
552
|
+
},
|
|
553
|
+
getAccessToken,
|
|
554
|
+
loginWithTabFlow,
|
|
496
555
|
loginWithWebAuthFlow
|
|
497
556
|
};
|
|
498
557
|
}
|
|
499
558
|
|
|
500
|
-
|
|
559
|
+
// src/adapters/extension/chrome-auth-client.ts
|
|
560
|
+
function hasChromeExtensionRuntime() {
|
|
561
|
+
const chrome = getChromeRuntime();
|
|
562
|
+
return Boolean(chrome.cookies && chrome.storage?.local && chrome.tabs);
|
|
563
|
+
}
|
|
564
|
+
var defaultSessionCookieName = "gs_auth_session";
|
|
565
|
+
var defaultAccessTokenStorageKey = "authToken";
|
|
566
|
+
var defaultTokenExpiresAtStorageKey = "authTokenExpiresAt";
|
|
567
|
+
var defaultSessionStorageKey = "authSession";
|
|
568
|
+
var defaultExtensionReturnToPath = "/oauth/complete";
|
|
569
|
+
function getChromeRuntime() {
|
|
570
|
+
return globalThis.chrome ?? {};
|
|
571
|
+
}
|
|
572
|
+
function createChromeExtensionAuthClient(options) {
|
|
573
|
+
const sessionCookieName = options.sessionCookieName?.trim() || defaultSessionCookieName;
|
|
574
|
+
const accessTokenStorageKey = options.accessTokenStorageKey?.trim() || defaultAccessTokenStorageKey;
|
|
575
|
+
const tokenExpiresAtStorageKey = options.tokenExpiresAtStorageKey?.trim() || defaultTokenExpiresAtStorageKey;
|
|
576
|
+
const sessionStorageKey = options.sessionStorageKey?.trim() || defaultSessionStorageKey;
|
|
577
|
+
const resolveSessionId = async () => {
|
|
578
|
+
const cookies = getChromeRuntime().cookies;
|
|
579
|
+
if (!cookies) {
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
const cookie = await cookies.get({
|
|
583
|
+
name: sessionCookieName,
|
|
584
|
+
url: `${options.apiOrigin}/`
|
|
585
|
+
});
|
|
586
|
+
return cookie?.value?.trim() || null;
|
|
587
|
+
};
|
|
588
|
+
const clearSessionId = async () => {
|
|
589
|
+
const cookies = getChromeRuntime().cookies;
|
|
590
|
+
if (!cookies) {
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
await cookies.remove({
|
|
594
|
+
name: sessionCookieName,
|
|
595
|
+
url: `${options.apiOrigin}/`
|
|
596
|
+
});
|
|
597
|
+
};
|
|
598
|
+
const persistAccessToken = async (token) => {
|
|
599
|
+
const storage = getChromeRuntime().storage?.local;
|
|
600
|
+
if (!storage) {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
if (!token) {
|
|
604
|
+
await storage.remove([accessTokenStorageKey, tokenExpiresAtStorageKey, sessionStorageKey]);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
await storage.set({
|
|
608
|
+
[accessTokenStorageKey]: token.accessToken,
|
|
609
|
+
[tokenExpiresAtStorageKey]: token.expiresAt || null,
|
|
610
|
+
[sessionStorageKey]: token.session || null
|
|
611
|
+
});
|
|
612
|
+
};
|
|
613
|
+
const restoreAccessToken = async () => {
|
|
614
|
+
const storage = getChromeRuntime().storage?.local;
|
|
615
|
+
if (!storage) {
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
const stored = await storage.get([
|
|
619
|
+
accessTokenStorageKey,
|
|
620
|
+
tokenExpiresAtStorageKey,
|
|
621
|
+
sessionStorageKey
|
|
622
|
+
]);
|
|
623
|
+
const rawAccessToken = stored[accessTokenStorageKey];
|
|
624
|
+
if (typeof rawAccessToken !== "string" || rawAccessToken.trim() === "") {
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
return {
|
|
628
|
+
accessToken: rawAccessToken,
|
|
629
|
+
application: options.application?.trim() || "",
|
|
630
|
+
expiresAt: typeof stored[tokenExpiresAtStorageKey] === "string" ? stored[tokenExpiresAtStorageKey] : null,
|
|
631
|
+
session: stored[sessionStorageKey] ?? null,
|
|
632
|
+
tokenType: "Bearer"
|
|
633
|
+
};
|
|
634
|
+
};
|
|
635
|
+
const openAuthorizePage = async ({ authorizeUrl }) => {
|
|
636
|
+
const tabs = getChromeRuntime().tabs;
|
|
637
|
+
if (!tabs) {
|
|
638
|
+
throw new Error("Chrome tabs API is unavailable.");
|
|
639
|
+
}
|
|
640
|
+
await tabs.create({ active: true, url: authorizeUrl });
|
|
641
|
+
};
|
|
642
|
+
return createCustomExtensionAuthClient({
|
|
643
|
+
...options,
|
|
644
|
+
defaultReturnToPath: options.defaultReturnToPath?.trim() || defaultExtensionReturnToPath,
|
|
645
|
+
clearSessionId,
|
|
646
|
+
openAuthorizePage,
|
|
647
|
+
persistAccessToken,
|
|
648
|
+
resolveSessionId,
|
|
649
|
+
restoreAccessToken
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// src/extension.ts
|
|
654
|
+
function hasCustomExtensionHooks(options) {
|
|
655
|
+
return Boolean(
|
|
656
|
+
options.clearSessionId || options.launchWebAuthFlow || options.openAuthorizePage || options.persistAccessToken || options.resolveSessionId || options.restoreAccessToken
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
function createExtensionAuthClient(options) {
|
|
660
|
+
if (hasChromeExtensionRuntime()) {
|
|
661
|
+
return createChromeExtensionAuthClient(options);
|
|
662
|
+
}
|
|
663
|
+
if (hasCustomExtensionHooks(options)) {
|
|
664
|
+
return createCustomExtensionAuthClient(options);
|
|
665
|
+
}
|
|
666
|
+
throw new AuthSdkError({
|
|
667
|
+
code: authErrorCode.apiError,
|
|
668
|
+
details: null,
|
|
669
|
+
message: "No supported extension runtime detected. Chromium browsers are supported automatically. Safari requires a dedicated adapter or explicit custom hooks.",
|
|
670
|
+
status: null
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
export { createChromeExtensionAuthClient, createCustomExtensionAuthClient, createExtensionAuthClient };
|
|
501
675
|
//# sourceMappingURL=extension.js.map
|
|
502
676
|
//# sourceMappingURL=extension.js.map
|