@learnorama/mozaik-sdk 0.1.0 → 0.5.0-staging.22.1
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/README.md +186 -5
- package/dist/api/account.d.ts +2 -2
- package/dist/api/account.js +1 -1
- package/dist/api/account.js.map +1 -1
- package/dist/api/admin-catalogue.d.ts +2 -2
- package/dist/api/admin-catalogue.js +1 -1
- package/dist/api/admin-catalogue.js.map +1 -1
- package/dist/api/admin-consumption.d.ts +7 -2
- package/dist/api/admin-consumption.js +6 -1
- package/dist/api/admin-consumption.js.map +1 -1
- package/dist/api/admin-licenses.d.ts +2 -2
- package/dist/api/admin-licenses.js +1 -1
- package/dist/api/admin-licenses.js.map +1 -1
- package/dist/api/admin-patrons.d.ts +3 -3
- package/dist/api/admin-patrons.js +2 -2
- package/dist/api/admin-patrons.js.map +1 -1
- package/dist/api/admin-reviews.d.ts +2 -2
- package/dist/api/admin-reviews.js +1 -1
- package/dist/api/admin-reviews.js.map +1 -1
- package/dist/api/admin-roles.d.ts +3 -3
- package/dist/api/admin-roles.js +2 -2
- package/dist/api/admin-roles.js.map +1 -1
- package/dist/api/admin-support.d.ts +2 -2
- package/dist/api/admin-support.js +1 -1
- package/dist/api/admin-support.js.map +1 -1
- package/dist/api/admin-tenants.d.ts +14 -2
- package/dist/api/admin-tenants.js +38 -2
- package/dist/api/admin-tenants.js.map +1 -1
- package/dist/api/admin-tutoring.d.ts +2 -2
- package/dist/api/admin-tutoring.js +1 -1
- package/dist/api/admin-tutoring.js.map +1 -1
- package/dist/api/auth.d.ts +2 -2
- package/dist/api/auth.js +1 -1
- package/dist/api/auth.js.map +1 -1
- package/dist/api/biblio-catalogue.d.ts +2 -2
- package/dist/api/biblio-catalogue.js +1 -1
- package/dist/api/biblio-catalogue.js.map +1 -1
- package/dist/api/biblio-home.d.ts +2 -2
- package/dist/api/biblio-home.js +1 -1
- package/dist/api/biblio-home.js.map +1 -1
- package/dist/api/biblio-library.d.ts +2 -2
- package/dist/api/biblio-library.js +1 -1
- package/dist/api/biblio-library.js.map +1 -1
- package/dist/api/biblio-nextory.d.ts +2 -2
- package/dist/api/biblio-nextory.js +1 -1
- package/dist/api/biblio-nextory.js.map +1 -1
- package/dist/api/biblio-press.d.ts +2 -2
- package/dist/api/biblio-press.js +1 -1
- package/dist/api/biblio-press.js.map +1 -1
- package/dist/api/biblio-search.d.ts +2 -2
- package/dist/api/biblio-search.js +1 -1
- package/dist/api/biblio-search.js.map +1 -1
- package/dist/api/biblio-telemetry.d.ts +2 -2
- package/dist/api/biblio-telemetry.js +1 -1
- package/dist/api/biblio-telemetry.js.map +1 -1
- package/dist/api/consumption.d.ts +2 -2
- package/dist/api/consumption.js +1 -1
- package/dist/api/consumption.js.map +1 -1
- package/dist/api/cvs-app.d.ts +2 -2
- package/dist/api/cvs-app.js +1 -1
- package/dist/api/cvs-app.js.map +1 -1
- package/dist/api/cvs-documents.d.ts +16 -4
- package/dist/api/cvs-documents.js +22 -5
- package/dist/api/cvs-documents.js.map +1 -1
- package/dist/api/cvs-favorites.d.ts +2 -2
- package/dist/api/cvs-favorites.js +1 -1
- package/dist/api/cvs-favorites.js.map +1 -1
- package/dist/api/cvs-ils.d.ts +167 -0
- package/dist/api/cvs-ils.js +362 -0
- package/dist/api/cvs-ils.js.map +1 -0
- package/dist/api/cvs-loans.d.ts +2 -2
- package/dist/api/cvs-loans.js +1 -1
- package/dist/api/cvs-loans.js.map +1 -1
- package/dist/api/cvs-payments.d.ts +49 -0
- package/dist/api/cvs-payments.js +84 -0
- package/dist/api/cvs-payments.js.map +1 -0
- package/dist/api/cvs-streaming.d.ts +2 -2
- package/dist/api/cvs-streaming.js +1 -1
- package/dist/api/cvs-streaming.js.map +1 -1
- package/dist/api/index.d.ts +5 -2
- package/dist/api/index.js +7 -4
- package/dist/api/partner-catalogue.d.ts +2 -2
- package/dist/api/partner-catalogue.js +1 -1
- package/dist/api/partner-catalogue.js.map +1 -1
- package/dist/api/partner-reader.d.ts +2 -2
- package/dist/api/partner-reader.js +1 -1
- package/dist/api/partner-reader.js.map +1 -1
- package/dist/api/profiles.d.ts +2 -2
- package/dist/api/profiles.js +1 -1
- package/dist/api/profiles.js.map +1 -1
- package/dist/api/search.d.ts +18 -0
- package/dist/api/search.js +37 -0
- package/dist/api/search.js.map +1 -0
- package/dist/api/tutoring-catalogue.d.ts +2 -2
- package/dist/api/tutoring-catalogue.js +1 -1
- package/dist/api/tutoring-catalogue.js.map +1 -1
- package/dist/api/tutoring-chat.d.ts +2 -2
- package/dist/api/tutoring-chat.js +1 -1
- package/dist/api/tutoring-chat.js.map +1 -1
- package/dist/api/tutoring-dispatch.d.ts +2 -2
- package/dist/api/tutoring-dispatch.js +1 -1
- package/dist/api/tutoring-dispatch.js.map +1 -1
- package/dist/api/tutoring-practitioner.d.ts +2 -2
- package/dist/api/tutoring-practitioner.js +1 -1
- package/dist/api/tutoring-practitioner.js.map +1 -1
- package/dist/api/tutoring-requests.d.ts +2 -2
- package/dist/api/tutoring-requests.js +1 -1
- package/dist/api/tutoring-requests.js.map +1 -1
- package/dist/auth.d.ts +10 -0
- package/dist/auth.js +451 -0
- package/dist/auth.js.map +1 -0
- package/dist/chat.d.ts +3 -0
- package/dist/chat.js +3 -0
- package/dist/{client-BDwBZYB0.d.ts → chunks/client.d.ts} +1 -1
- package/dist/{client-D3kMOcIr.js → chunks/client.js} +1 -1
- package/dist/chunks/client.js.map +1 -0
- package/dist/chunks/client2.d.ts +53 -0
- package/dist/chunks/connection.d.ts +28 -0
- package/dist/chunks/connection.js +141 -0
- package/dist/chunks/connection.js.map +1 -0
- package/dist/{index-wL_20vR8.d.ts → chunks/index.d.ts} +922 -128
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +2 -2
- package/dist/models.js +44 -1
- package/dist/models.js.map +1 -1
- package/dist/react/account.d.ts +2 -2
- package/dist/react/account.js +2 -2
- package/dist/react/account.js.map +1 -1
- package/dist/react/admin-catalogue.d.ts +2 -2
- package/dist/react/admin-catalogue.js +2 -2
- package/dist/react/admin-catalogue.js.map +1 -1
- package/dist/react/admin-consumption.d.ts +7 -2
- package/dist/react/admin-consumption.js +7 -2
- package/dist/react/admin-consumption.js.map +1 -1
- package/dist/react/admin-licenses.d.ts +2 -2
- package/dist/react/admin-licenses.js +2 -2
- package/dist/react/admin-licenses.js.map +1 -1
- package/dist/react/admin-patrons.d.ts +3 -3
- package/dist/react/admin-patrons.js +3 -3
- package/dist/react/admin-patrons.js.map +1 -1
- package/dist/react/admin-reviews.d.ts +2 -2
- package/dist/react/admin-reviews.js +2 -2
- package/dist/react/admin-reviews.js.map +1 -1
- package/dist/react/admin-roles.d.ts +3 -3
- package/dist/react/admin-roles.js +3 -3
- package/dist/react/admin-roles.js.map +1 -1
- package/dist/react/admin-support.d.ts +2 -2
- package/dist/react/admin-support.js +2 -2
- package/dist/react/admin-support.js.map +1 -1
- package/dist/react/admin-tenants.d.ts +68 -2
- package/dist/react/admin-tenants.js +87 -3
- package/dist/react/admin-tenants.js.map +1 -1
- package/dist/react/admin-tutoring.d.ts +2 -2
- package/dist/react/admin-tutoring.js +2 -2
- package/dist/react/admin-tutoring.js.map +1 -1
- package/dist/react/auth.d.ts +2 -2
- package/dist/react/auth.js +2 -2
- package/dist/react/auth.js.map +1 -1
- package/dist/react/biblio-catalogue.d.ts +2 -2
- package/dist/react/biblio-catalogue.js +2 -2
- package/dist/react/biblio-catalogue.js.map +1 -1
- package/dist/react/biblio-home.d.ts +2 -2
- package/dist/react/biblio-home.js +2 -2
- package/dist/react/biblio-home.js.map +1 -1
- package/dist/react/biblio-library.d.ts +2 -2
- package/dist/react/biblio-library.js +2 -2
- package/dist/react/biblio-library.js.map +1 -1
- package/dist/react/biblio-nextory.d.ts +2 -2
- package/dist/react/biblio-nextory.js +2 -2
- package/dist/react/biblio-nextory.js.map +1 -1
- package/dist/react/biblio-press.d.ts +2 -2
- package/dist/react/biblio-press.js +2 -2
- package/dist/react/biblio-press.js.map +1 -1
- package/dist/react/biblio-search.d.ts +2 -2
- package/dist/react/biblio-search.js +2 -2
- package/dist/react/biblio-search.js.map +1 -1
- package/dist/react/biblio-telemetry.d.ts +2 -2
- package/dist/react/biblio-telemetry.js +2 -2
- package/dist/react/biblio-telemetry.js.map +1 -1
- package/dist/react/chat.d.ts +36 -0
- package/dist/react/chat.js +234 -0
- package/dist/react/chat.js.map +1 -0
- package/dist/react/consumption.d.ts +2 -2
- package/dist/react/consumption.js +2 -2
- package/dist/react/consumption.js.map +1 -1
- package/dist/react/cvs-app.d.ts +2 -2
- package/dist/react/cvs-app.js +2 -2
- package/dist/react/cvs-app.js.map +1 -1
- package/dist/react/cvs-documents.d.ts +17 -4
- package/dist/react/cvs-documents.js +25 -8
- package/dist/react/cvs-documents.js.map +1 -1
- package/dist/react/cvs-favorites.d.ts +2 -2
- package/dist/react/cvs-favorites.js +2 -2
- package/dist/react/cvs-favorites.js.map +1 -1
- package/dist/react/cvs-ils.d.ts +552 -0
- package/dist/react/cvs-ils.js +822 -0
- package/dist/react/cvs-ils.js.map +1 -0
- package/dist/react/cvs-loans.d.ts +2 -2
- package/dist/react/cvs-loans.js +2 -2
- package/dist/react/cvs-loans.js.map +1 -1
- package/dist/react/cvs-payments.d.ts +123 -0
- package/dist/react/cvs-payments.js +181 -0
- package/dist/react/cvs-payments.js.map +1 -0
- package/dist/react/cvs-streaming.d.ts +2 -2
- package/dist/react/cvs-streaming.js +2 -2
- package/dist/react/cvs-streaming.js.map +1 -1
- package/dist/react/index.d.ts +5 -2
- package/dist/react/index.js +5 -2
- package/dist/react/partner-catalogue.d.ts +2 -2
- package/dist/react/partner-catalogue.js +2 -2
- package/dist/react/partner-catalogue.js.map +1 -1
- package/dist/react/partner-reader.d.ts +2 -2
- package/dist/react/partner-reader.js +2 -2
- package/dist/react/partner-reader.js.map +1 -1
- package/dist/react/profiles.d.ts +2 -2
- package/dist/react/profiles.js +2 -2
- package/dist/react/profiles.js.map +1 -1
- package/dist/react/search.d.ts +47 -0
- package/dist/react/search.js +81 -0
- package/dist/react/search.js.map +1 -0
- package/dist/react/sso.d.ts +15 -0
- package/dist/react/sso.js +48 -0
- package/dist/react/sso.js.map +1 -0
- package/dist/react/tutoring-catalogue.d.ts +2 -2
- package/dist/react/tutoring-catalogue.js +2 -2
- package/dist/react/tutoring-catalogue.js.map +1 -1
- package/dist/react/tutoring-chat.d.ts +2 -2
- package/dist/react/tutoring-chat.js +2 -2
- package/dist/react/tutoring-chat.js.map +1 -1
- package/dist/react/tutoring-dispatch.d.ts +2 -2
- package/dist/react/tutoring-dispatch.js +2 -2
- package/dist/react/tutoring-dispatch.js.map +1 -1
- package/dist/react/tutoring-practitioner.d.ts +2 -2
- package/dist/react/tutoring-practitioner.js +2 -2
- package/dist/react/tutoring-practitioner.js.map +1 -1
- package/dist/react/tutoring-requests.d.ts +2 -2
- package/dist/react/tutoring-requests.js +2 -2
- package/dist/react/tutoring-requests.js.map +1 -1
- package/package.json +29 -1
- package/dist/client-D3kMOcIr.js.map +0 -1
package/dist/auth.js
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
|
|
2
|
+
import { n as createClient, t as ApiError } from "./chunks/client.js";
|
|
3
|
+
import { authorization, createSessionFromCode, deleteSession, refreshSession } from "./api/auth.js";
|
|
4
|
+
//#region src/auth/transactions.ts
|
|
5
|
+
var AuthError = class extends Error {
|
|
6
|
+
code;
|
|
7
|
+
constructor(code, message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.name = "AuthError";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const transactionLifetime = 6e5;
|
|
14
|
+
const oauthParameters = [
|
|
15
|
+
"code",
|
|
16
|
+
"state",
|
|
17
|
+
"iss",
|
|
18
|
+
"session_state",
|
|
19
|
+
"error",
|
|
20
|
+
"error_description",
|
|
21
|
+
"error_uri"
|
|
22
|
+
];
|
|
23
|
+
function callbackAddress(value) {
|
|
24
|
+
const url = new URL(value);
|
|
25
|
+
if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) throw new TypeError("redirectUri must be an absolute HTTP(S) callback URL without credentials, query or fragment");
|
|
26
|
+
return url;
|
|
27
|
+
}
|
|
28
|
+
function localReturnTo(value, origin) {
|
|
29
|
+
if (!value.startsWith("/") || value.startsWith("//") || /[\\\u0000-\u0020\u007f]/.test(value)) throw new AuthError("invalid_return_to", "returnTo must be a local absolute path");
|
|
30
|
+
const url = new URL(value, origin);
|
|
31
|
+
if (url.origin !== origin) throw new AuthError("invalid_return_to", "returnTo must stay on this application");
|
|
32
|
+
return url.pathname + url.search + url.hash;
|
|
33
|
+
}
|
|
34
|
+
function randomSecret() {
|
|
35
|
+
return base64url(crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(32)));
|
|
36
|
+
}
|
|
37
|
+
function base64url(bytes) {
|
|
38
|
+
return btoa(String.fromCharCode(...bytes)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
39
|
+
}
|
|
40
|
+
async function challenge(verifier) {
|
|
41
|
+
return base64url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier))));
|
|
42
|
+
}
|
|
43
|
+
function transactionStore(baseUrl, redirect, supplied) {
|
|
44
|
+
const prefix = `mozaik:sso:${encodeURIComponent(baseUrl)}:${encodeURIComponent(redirect.href)}:`;
|
|
45
|
+
const storage = () => {
|
|
46
|
+
if (supplied) return supplied;
|
|
47
|
+
if (typeof window === "undefined") throw new AuthError("browser_required", "SSO transactions require browser sessionStorage");
|
|
48
|
+
if (window.location.origin !== redirect.origin) throw new AuthError("invalid_origin", "Start SSO on the callback application origin");
|
|
49
|
+
return window.sessionStorage;
|
|
50
|
+
};
|
|
51
|
+
const keys = (store) => Array.from({ length: store.length }, (_, index) => store.key(index)).filter((key) => key !== null && key.startsWith(prefix));
|
|
52
|
+
return {
|
|
53
|
+
save(state, transaction) {
|
|
54
|
+
const store = storage();
|
|
55
|
+
for (const key of keys(store)) try {
|
|
56
|
+
const old = JSON.parse(store.getItem(key));
|
|
57
|
+
if (!Number.isFinite(old.createdAt) || Date.now() - old.createdAt >= 6e5) store.removeItem(key);
|
|
58
|
+
} catch {
|
|
59
|
+
store.removeItem(key);
|
|
60
|
+
}
|
|
61
|
+
store.setItem(prefix + state, JSON.stringify(transaction));
|
|
62
|
+
},
|
|
63
|
+
consume(state) {
|
|
64
|
+
if (!/^[A-Za-z0-9_-]{43}$/.test(state)) throw new AuthError("invalid_state", "Invalid SSO state");
|
|
65
|
+
const store = storage();
|
|
66
|
+
const saved = store.getItem(prefix + state);
|
|
67
|
+
if (!saved) throw new AuthError("invalid_state", "SSO transaction is missing or already consumed");
|
|
68
|
+
store.removeItem(prefix + state);
|
|
69
|
+
let value;
|
|
70
|
+
try {
|
|
71
|
+
value = JSON.parse(saved);
|
|
72
|
+
} catch {
|
|
73
|
+
throw new AuthError("invalid_state", "Invalid SSO transaction");
|
|
74
|
+
}
|
|
75
|
+
if (!value || !/^[A-Za-z0-9_-]{43}$/.test(value.verifier) || typeof value.issuer !== "string" || typeof value.returnTo !== "string" || !(value.provider === null || typeof value.provider === "string")) throw new AuthError("invalid_state", "Invalid SSO transaction");
|
|
76
|
+
if (!Number.isFinite(value.createdAt) || value.createdAt > Date.now() || Date.now() - value.createdAt >= 6e5) throw new AuthError("expired_transaction", "SSO transaction expired; start sign-in again");
|
|
77
|
+
value.returnTo = localReturnTo(value.returnTo, redirect.origin);
|
|
78
|
+
return value;
|
|
79
|
+
},
|
|
80
|
+
clear() {
|
|
81
|
+
if (!supplied && typeof window === "undefined") return;
|
|
82
|
+
const store = storage();
|
|
83
|
+
for (const key of keys(store)) store.removeItem(key);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function parseCallback(value, redirect) {
|
|
88
|
+
const url = new URL(value);
|
|
89
|
+
if (url.origin !== redirect.origin || url.pathname !== redirect.pathname || url.username || url.password || url.hash) throw new AuthError("invalid_callback", "SSO callback does not match redirectUri");
|
|
90
|
+
cleanCallback(url);
|
|
91
|
+
if (oauthParameters.some((key) => url.searchParams.getAll(key).length > 1)) throw new AuthError("invalid_callback", "SSO callback contains duplicate parameters");
|
|
92
|
+
return url;
|
|
93
|
+
}
|
|
94
|
+
function cleanCallback(url) {
|
|
95
|
+
const clean = new URL(url);
|
|
96
|
+
for (const key of oauthParameters) clean.searchParams.delete(key);
|
|
97
|
+
if (typeof window !== "undefined" && window.location.href === url.href) window.history.replaceState(window.history.state, "", clean.href);
|
|
98
|
+
return clean.href;
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/auth/client.ts
|
|
102
|
+
const toError = (value) => value instanceof Error ? value : new AuthError("request_failed", "Authentication request failed");
|
|
103
|
+
const changed = () => new AuthError("session_changed", "Authentication session changed");
|
|
104
|
+
/** Browser SSO through Mozaik's account-provisioning backend, with no client secret. */
|
|
105
|
+
function createMozaikAuth(options) {
|
|
106
|
+
const redirect = callbackAddress(options.redirectUri);
|
|
107
|
+
const publicClient = createClient({
|
|
108
|
+
baseUrl: options.baseUrl,
|
|
109
|
+
fetch: options.fetch
|
|
110
|
+
});
|
|
111
|
+
const transactions = transactionStore(publicClient.baseUrl, redirect, options.transactionStorage);
|
|
112
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
113
|
+
const pending = /* @__PURE__ */ new Set();
|
|
114
|
+
const callbacks = /* @__PURE__ */ new Map();
|
|
115
|
+
let completed;
|
|
116
|
+
let disposed = false, epoch = 0;
|
|
117
|
+
let session;
|
|
118
|
+
let error = null;
|
|
119
|
+
let abort = new AbortController();
|
|
120
|
+
let timer;
|
|
121
|
+
let flight;
|
|
122
|
+
let client = makeClient();
|
|
123
|
+
let snapshot = makeSnapshot();
|
|
124
|
+
function assertUsable() {
|
|
125
|
+
if (disposed) throw new AuthError("disposed", "Authentication client was disposed");
|
|
126
|
+
}
|
|
127
|
+
function assertEpoch(expected) {
|
|
128
|
+
assertUsable();
|
|
129
|
+
if (epoch !== expected) throw changed();
|
|
130
|
+
}
|
|
131
|
+
function makeSnapshot() {
|
|
132
|
+
return Object.freeze({
|
|
133
|
+
status: pending.size ? "authenticating" : flight?.epoch === epoch ? "refreshing" : session ? "authenticated" : "anonymous",
|
|
134
|
+
isAuthenticated: Boolean(session),
|
|
135
|
+
isLoading: pending.size > 0 || flight?.epoch === epoch,
|
|
136
|
+
account: session?.data.account ?? null,
|
|
137
|
+
principal: session?.data.principal ?? null,
|
|
138
|
+
error,
|
|
139
|
+
client
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function publish() {
|
|
143
|
+
snapshot = makeSnapshot();
|
|
144
|
+
for (const listener of listeners) listener();
|
|
145
|
+
}
|
|
146
|
+
function clearSession() {
|
|
147
|
+
epoch++;
|
|
148
|
+
abort.abort();
|
|
149
|
+
abort = new AbortController();
|
|
150
|
+
clearTimeout(timer);
|
|
151
|
+
timer = void 0;
|
|
152
|
+
session = void 0;
|
|
153
|
+
pending.clear();
|
|
154
|
+
flight = void 0;
|
|
155
|
+
completed = void 0;
|
|
156
|
+
client = makeClient();
|
|
157
|
+
}
|
|
158
|
+
function makeClient() {
|
|
159
|
+
const boundEpoch = epoch, boundSignal = abort.signal;
|
|
160
|
+
return createClient({
|
|
161
|
+
baseUrl: publicClient.baseUrl,
|
|
162
|
+
getAccessToken: async () => {
|
|
163
|
+
assertEpoch(boundEpoch);
|
|
164
|
+
if (session && Date.now() >= session.refreshAt) try {
|
|
165
|
+
await refresh();
|
|
166
|
+
} catch (failure) {
|
|
167
|
+
if (!session || Date.now() >= session.expiresAt) throw failure;
|
|
168
|
+
}
|
|
169
|
+
assertEpoch(boundEpoch);
|
|
170
|
+
return session?.data.access_token;
|
|
171
|
+
},
|
|
172
|
+
fetch: async (url, init) => {
|
|
173
|
+
assertEpoch(boundEpoch);
|
|
174
|
+
const signal = init?.signal ? AbortSignal.any([init.signal, boundSignal]) : boundSignal;
|
|
175
|
+
signal.throwIfAborted();
|
|
176
|
+
const response = await (options.fetch ?? globalThis.fetch)(url, {
|
|
177
|
+
...init,
|
|
178
|
+
signal
|
|
179
|
+
});
|
|
180
|
+
assertEpoch(boundEpoch);
|
|
181
|
+
return response;
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
function validateSession(data) {
|
|
186
|
+
if (!data || typeof data.access_token !== "string" || !data.access_token || !Number.isFinite(data.expires_in) || data.expires_in <= 0 || !data.account || !data.principal || typeof data.principal.subject !== "string" || data.refresh_token != null && (typeof data.refresh_token !== "string" || !data.refresh_token)) throw new AuthError("invalid_response", "Invalid authentication session response");
|
|
187
|
+
}
|
|
188
|
+
function schedule(delay) {
|
|
189
|
+
clearTimeout(timer);
|
|
190
|
+
timer = setTimeout(() => {
|
|
191
|
+
refresh().catch(() => {});
|
|
192
|
+
}, Math.min(2147483647, Math.max(0, delay)));
|
|
193
|
+
timer.unref?.();
|
|
194
|
+
}
|
|
195
|
+
function install(data) {
|
|
196
|
+
validateSession(data);
|
|
197
|
+
const now = Date.now(), ttl = data.expires_in * 1e3;
|
|
198
|
+
session = {
|
|
199
|
+
data,
|
|
200
|
+
expiresAt: now + ttl,
|
|
201
|
+
refreshAt: now + ttl - (data.refresh_token ? Math.min(3e4, ttl / 2) : 0),
|
|
202
|
+
refreshExpiresAt: data.refresh_expires_in && data.refresh_expires_in > 0 ? now + data.refresh_expires_in * 1e3 : void 0
|
|
203
|
+
};
|
|
204
|
+
error = null;
|
|
205
|
+
schedule(session.refreshAt - now);
|
|
206
|
+
}
|
|
207
|
+
async function revoke(token) {
|
|
208
|
+
if (token) await deleteSession({ refresh_token: token }, {
|
|
209
|
+
client: publicClient,
|
|
210
|
+
signal: AbortSignal.timeout(2e4)
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
function refresh() {
|
|
214
|
+
assertUsable();
|
|
215
|
+
if (flight?.epoch === epoch) return flight.applied;
|
|
216
|
+
const current = session, expected = epoch;
|
|
217
|
+
if (!current) return Promise.reject(new AuthError("session_expired", "Sign in before refreshing a session"));
|
|
218
|
+
if (!current.data.refresh_token || current.refreshExpiresAt && Date.now() >= current.refreshExpiresAt) {
|
|
219
|
+
const expired = new AuthError("session_expired", "Session expired; sign in again");
|
|
220
|
+
clearSession();
|
|
221
|
+
error = expired;
|
|
222
|
+
publish();
|
|
223
|
+
return Promise.reject(expired);
|
|
224
|
+
}
|
|
225
|
+
const requestAbort = new AbortController();
|
|
226
|
+
const response = refreshSession({ refresh_token: current.data.refresh_token }, {
|
|
227
|
+
client: publicClient,
|
|
228
|
+
signal: AbortSignal.any([requestAbort.signal, AbortSignal.timeout(2e4)])
|
|
229
|
+
});
|
|
230
|
+
const running = {
|
|
231
|
+
epoch: expected,
|
|
232
|
+
response,
|
|
233
|
+
applied: Promise.resolve(),
|
|
234
|
+
abort: requestAbort
|
|
235
|
+
};
|
|
236
|
+
flight = running;
|
|
237
|
+
running.applied = response.then((data) => {
|
|
238
|
+
assertEpoch(expected);
|
|
239
|
+
validateSession(data);
|
|
240
|
+
if (data.principal.subject !== current.data.principal.subject || data.account.id !== current.data.account.id) throw new AuthError("invalid_response", "Refresh changed the authenticated account");
|
|
241
|
+
install(data);
|
|
242
|
+
}).catch((failure) => {
|
|
243
|
+
if (!disposed && expected === epoch) {
|
|
244
|
+
error = toError(failure);
|
|
245
|
+
if (failure instanceof ApiError && [401, 403].includes(failure.status) || failure instanceof AuthError || Date.now() >= current.expiresAt) clearSession();
|
|
246
|
+
else {
|
|
247
|
+
current.refreshAt = Math.min(current.expiresAt, Date.now() + 5e3);
|
|
248
|
+
schedule(current.refreshAt - Date.now());
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
throw failure;
|
|
252
|
+
}).finally(() => {
|
|
253
|
+
if (flight === running) flight = void 0;
|
|
254
|
+
if (!disposed && expected === epoch) publish();
|
|
255
|
+
else if (!disposed && !session) publish();
|
|
256
|
+
});
|
|
257
|
+
publish();
|
|
258
|
+
return running.applied;
|
|
259
|
+
}
|
|
260
|
+
async function prepare(provider, settings = {}) {
|
|
261
|
+
assertUsable();
|
|
262
|
+
if (provider !== void 0 && (!provider.trim() || provider.length > 64)) throw new TypeError("A registered provider alias is required");
|
|
263
|
+
const returnTo = localReturnTo(settings.returnTo ?? "/", redirect.origin);
|
|
264
|
+
const expected = epoch, operation = Symbol();
|
|
265
|
+
pending.add(operation);
|
|
266
|
+
error = null;
|
|
267
|
+
publish();
|
|
268
|
+
try {
|
|
269
|
+
const state = randomSecret(), verifier = randomSecret(), codeChallenge = await challenge(verifier);
|
|
270
|
+
assertEpoch(expected);
|
|
271
|
+
const result = await authorization({
|
|
272
|
+
redirect_uri: redirect.href,
|
|
273
|
+
code_challenge: codeChallenge,
|
|
274
|
+
state,
|
|
275
|
+
provider,
|
|
276
|
+
prompt: settings.prompt
|
|
277
|
+
}, {
|
|
278
|
+
client: publicClient,
|
|
279
|
+
signal: AbortSignal.any([abort.signal, AbortSignal.timeout(2e4)])
|
|
280
|
+
});
|
|
281
|
+
assertEpoch(expected);
|
|
282
|
+
const url = new URL(result.authorizationUrl);
|
|
283
|
+
if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.hash || !url.pathname.endsWith("/protocol/openid-connect/auth") || [
|
|
284
|
+
"state",
|
|
285
|
+
"code_challenge",
|
|
286
|
+
"code_challenge_method",
|
|
287
|
+
"redirect_uri",
|
|
288
|
+
"response_type"
|
|
289
|
+
].some((key) => url.searchParams.getAll(key).length !== 1) || url.searchParams.get("state") !== state || url.searchParams.get("code_challenge") !== codeChallenge || url.searchParams.get("code_challenge_method") !== "S256" || url.searchParams.get("redirect_uri") !== redirect.href || url.searchParams.get("response_type") !== "code" || provider === "google" && result.provider !== "google") throw new AuthError("invalid_response", "Invalid SSO authorization response");
|
|
290
|
+
const createdAt = Date.now();
|
|
291
|
+
transactions.save(state, {
|
|
292
|
+
verifier,
|
|
293
|
+
createdAt,
|
|
294
|
+
returnTo,
|
|
295
|
+
provider: result.provider ?? null,
|
|
296
|
+
issuer: url.origin + url.pathname.slice(0, -29)
|
|
297
|
+
});
|
|
298
|
+
return {
|
|
299
|
+
url: url.href,
|
|
300
|
+
provider: result.provider ?? null,
|
|
301
|
+
expiresAt: createdAt + transactionLifetime
|
|
302
|
+
};
|
|
303
|
+
} catch (failure) {
|
|
304
|
+
if (expected === epoch && !disposed) error = toError(failure);
|
|
305
|
+
throw failure;
|
|
306
|
+
} finally {
|
|
307
|
+
pending.delete(operation);
|
|
308
|
+
if (expected === epoch && !disposed) publish();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function completeSso(callbackUrl) {
|
|
312
|
+
try {
|
|
313
|
+
assertUsable();
|
|
314
|
+
const url = parseCallback(callbackUrl ?? (typeof window === "undefined" ? "" : window.location.href), redirect);
|
|
315
|
+
const state = url.searchParams.get("state") ?? "";
|
|
316
|
+
if (callbackUrl === void 0 && !state) {
|
|
317
|
+
const pendingCallback = [...callbacks.values()].find((item) => item.cleanUrl === url.href);
|
|
318
|
+
if (pendingCallback) return pendingCallback.promise;
|
|
319
|
+
if (completed?.epoch === epoch && completed.url === url.href) return Promise.resolve(completed.result);
|
|
320
|
+
}
|
|
321
|
+
const existing = callbacks.get(state);
|
|
322
|
+
if (existing) {
|
|
323
|
+
if (existing.url !== url.href) throw new AuthError("invalid_callback", "A different callback is already using this state");
|
|
324
|
+
return existing.promise;
|
|
325
|
+
}
|
|
326
|
+
const cleanUrl = cleanCallback(url);
|
|
327
|
+
const transaction = transactions.consume(state);
|
|
328
|
+
if (url.searchParams.has("iss") && url.searchParams.get("iss") !== transaction.issuer) throw new AuthError("invalid_issuer", "Unexpected SSO callback issuer");
|
|
329
|
+
if (url.searchParams.has("error")) throw new AuthError("provider_error", "Sign-in was refused or cancelled by the identity provider");
|
|
330
|
+
const code = url.searchParams.get("code");
|
|
331
|
+
if (!code || code.length > 4096) throw new AuthError("invalid_callback", "SSO callback is missing a valid authorization code");
|
|
332
|
+
if (session || flight || callbacks.size || pending.size) clearSession();
|
|
333
|
+
const expected = epoch, operation = Symbol();
|
|
334
|
+
pending.add(operation);
|
|
335
|
+
error = null;
|
|
336
|
+
publish();
|
|
337
|
+
const completion = (async () => {
|
|
338
|
+
try {
|
|
339
|
+
const data = await createSessionFromCode({
|
|
340
|
+
code,
|
|
341
|
+
codeVerifier: transaction.verifier,
|
|
342
|
+
redirectUri: redirect.href,
|
|
343
|
+
state
|
|
344
|
+
}, {
|
|
345
|
+
client: publicClient,
|
|
346
|
+
signal: AbortSignal.timeout(2e4)
|
|
347
|
+
});
|
|
348
|
+
if (disposed || expected !== epoch) {
|
|
349
|
+
await revoke(data.refresh_token).catch(() => {});
|
|
350
|
+
throw changed();
|
|
351
|
+
}
|
|
352
|
+
validateSession(data);
|
|
353
|
+
if (transaction.provider && data.principal.provider !== transaction.provider) {
|
|
354
|
+
await revoke(data.refresh_token).catch(() => {});
|
|
355
|
+
throw new AuthError("provider_mismatch", "Sign-in completed with a different identity provider");
|
|
356
|
+
}
|
|
357
|
+
clearSession();
|
|
358
|
+
install(data);
|
|
359
|
+
const result = {
|
|
360
|
+
account: data.account,
|
|
361
|
+
principal: data.principal,
|
|
362
|
+
returnTo: transaction.returnTo
|
|
363
|
+
};
|
|
364
|
+
completed = {
|
|
365
|
+
epoch,
|
|
366
|
+
url: cleanUrl,
|
|
367
|
+
result
|
|
368
|
+
};
|
|
369
|
+
publish();
|
|
370
|
+
return result;
|
|
371
|
+
} catch (failure) {
|
|
372
|
+
if (!disposed && expected === epoch) {
|
|
373
|
+
error = toError(failure);
|
|
374
|
+
publish();
|
|
375
|
+
}
|
|
376
|
+
throw failure;
|
|
377
|
+
} finally {
|
|
378
|
+
callbacks.delete(state);
|
|
379
|
+
pending.delete(operation);
|
|
380
|
+
if (!disposed) publish();
|
|
381
|
+
}
|
|
382
|
+
})();
|
|
383
|
+
callbacks.set(state, {
|
|
384
|
+
url: url.href,
|
|
385
|
+
cleanUrl,
|
|
386
|
+
promise: completion
|
|
387
|
+
});
|
|
388
|
+
return completion;
|
|
389
|
+
} catch (failure) {
|
|
390
|
+
if (!disposed) {
|
|
391
|
+
error = toError(failure);
|
|
392
|
+
publish();
|
|
393
|
+
}
|
|
394
|
+
return Promise.reject(failure);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
get client() {
|
|
399
|
+
return client;
|
|
400
|
+
},
|
|
401
|
+
getSnapshot: () => snapshot,
|
|
402
|
+
subscribe(listener) {
|
|
403
|
+
listeners.add(listener);
|
|
404
|
+
return () => {
|
|
405
|
+
listeners.delete(listener);
|
|
406
|
+
};
|
|
407
|
+
},
|
|
408
|
+
prepareInternalSso: (settings) => prepare("google", settings),
|
|
409
|
+
prepareProviderSso: (alias, settings) => prepare(alias, settings),
|
|
410
|
+
prepareSso: (settings) => prepare(void 0, settings),
|
|
411
|
+
completeSso,
|
|
412
|
+
refresh,
|
|
413
|
+
async logout() {
|
|
414
|
+
assertUsable();
|
|
415
|
+
const old = session, rotating = flight;
|
|
416
|
+
clearSession();
|
|
417
|
+
error = null;
|
|
418
|
+
publish();
|
|
419
|
+
const expected = epoch;
|
|
420
|
+
let cleanupError;
|
|
421
|
+
try {
|
|
422
|
+
transactions.clear();
|
|
423
|
+
} catch (failure) {
|
|
424
|
+
cleanupError = failure;
|
|
425
|
+
}
|
|
426
|
+
try {
|
|
427
|
+
await revoke((await rotating?.response.catch(() => void 0))?.refresh_token ?? old?.data.refresh_token);
|
|
428
|
+
if (cleanupError) throw cleanupError;
|
|
429
|
+
} catch (failure) {
|
|
430
|
+
if (!disposed && expected === epoch) {
|
|
431
|
+
error = toError(failure);
|
|
432
|
+
publish();
|
|
433
|
+
}
|
|
434
|
+
throw failure;
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
dispose() {
|
|
438
|
+
if (disposed) return;
|
|
439
|
+
flight?.abort.abort();
|
|
440
|
+
clearSession();
|
|
441
|
+
disposed = true;
|
|
442
|
+
error = null;
|
|
443
|
+
publish();
|
|
444
|
+
listeners.clear();
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
//#endregion
|
|
449
|
+
export { AuthError, createMozaikAuth };
|
|
450
|
+
|
|
451
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","names":[],"sources":["../src/auth/transactions.ts","../src/auth/client.ts"],"sourcesContent":["// Handwritten. This directory is never an Orval output/cleanup target.\nexport interface Transaction {\n verifier: string;\n createdAt: number;\n returnTo: string;\n issuer: string;\n provider: string | null;\n}\n\nexport class AuthError extends Error {\n constructor(public readonly code: string, message: string) {\n super(message);\n this.name = 'AuthError';\n }\n}\n\nexport const transactionLifetime = 10 * 60_000;\nconst oauthParameters = ['code', 'state', 'iss', 'session_state', 'error', 'error_description', 'error_uri'];\n\nexport function callbackAddress(value: string): URL {\n const url = new URL(value);\n if (!['https:', 'http:'].includes(url.protocol) || url.username || url.password || url.search || url.hash) {\n throw new TypeError('redirectUri must be an absolute HTTP(S) callback URL without credentials, query or fragment');\n }\n return url;\n}\n\nexport function localReturnTo(value: string, origin: string): string {\n // Reject protocol-relative URLs and browser-normalized slash/control tricks.\n if (!value.startsWith('/') || value.startsWith('//') || /[\\\\\\u0000-\\u0020\\u007f]/.test(value)) {\n throw new AuthError('invalid_return_to', 'returnTo must be a local absolute path');\n }\n const url = new URL(value, origin);\n if (url.origin !== origin) throw new AuthError('invalid_return_to', 'returnTo must stay on this application');\n return url.pathname + url.search + url.hash;\n}\n\nexport function randomSecret(): string {\n return base64url(crypto.getRandomValues(new Uint8Array(32)));\n}\nfunction base64url(bytes: Uint8Array): string {\n return btoa(String.fromCharCode(...bytes)).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '');\n}\nexport async function challenge(verifier: string): Promise<string> {\n return base64url(new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier))));\n}\n\nexport function transactionStore(baseUrl: string, redirect: URL, supplied?: Storage) {\n const prefix = `mozaik:sso:${encodeURIComponent(baseUrl)}:${encodeURIComponent(redirect.href)}:`;\n const storage = () => {\n if (supplied) return supplied;\n if (typeof window === 'undefined') throw new AuthError('browser_required', 'SSO transactions require browser sessionStorage');\n if (window.location.origin !== redirect.origin) throw new AuthError('invalid_origin', 'Start SSO on the callback application origin');\n return window.sessionStorage;\n };\n const keys = (store: Storage) => Array.from({ length: store.length }, (_, index) => store.key(index))\n .filter((key): key is string => key !== null && key.startsWith(prefix));\n return {\n save(state: string, transaction: Transaction) {\n const store = storage();\n for (const key of keys(store)) {\n try {\n const old = JSON.parse(store.getItem(key)!);\n if (!Number.isFinite(old.createdAt) || Date.now() - old.createdAt >= transactionLifetime) store.removeItem(key);\n } catch { store.removeItem(key); }\n }\n store.setItem(prefix + state, JSON.stringify(transaction));\n },\n consume(state: string): Transaction {\n if (!/^[A-Za-z0-9_-]{43}$/.test(state)) throw new AuthError('invalid_state', 'Invalid SSO state');\n const store = storage();\n const saved = store.getItem(prefix + state);\n if (!saved) throw new AuthError('invalid_state', 'SSO transaction is missing or already consumed');\n store.removeItem(prefix + state);\n let value: Transaction;\n try { value = JSON.parse(saved); }\n catch { throw new AuthError('invalid_state', 'Invalid SSO transaction'); }\n if (!value || !/^[A-Za-z0-9_-]{43}$/.test(value.verifier) || typeof value.issuer !== 'string' ||\n typeof value.returnTo !== 'string' || !(value.provider === null || typeof value.provider === 'string')) {\n throw new AuthError('invalid_state', 'Invalid SSO transaction');\n }\n if (!Number.isFinite(value.createdAt) || value.createdAt > Date.now() || Date.now() - value.createdAt >= transactionLifetime) {\n throw new AuthError('expired_transaction', 'SSO transaction expired; start sign-in again');\n }\n value.returnTo = localReturnTo(value.returnTo, redirect.origin);\n return value;\n },\n clear() {\n // Factory creation/disposal on the server does not access browser globals.\n if (!supplied && typeof window === 'undefined') return;\n const store = storage();\n for (const key of keys(store)) store.removeItem(key);\n },\n };\n}\n\nexport function parseCallback(value: string, redirect: URL): URL {\n const url = new URL(value);\n if (url.origin !== redirect.origin || url.pathname !== redirect.pathname || url.username || url.password || url.hash) {\n throw new AuthError('invalid_callback', 'SSO callback does not match redirectUri');\n }\n cleanCallback(url);\n if (oauthParameters.some(key => url.searchParams.getAll(key).length > 1)) {\n throw new AuthError('invalid_callback', 'SSO callback contains duplicate parameters');\n }\n return url;\n}\n\nexport function cleanCallback(url: URL): string {\n const clean = new URL(url);\n for (const key of oauthParameters) clean.searchParams.delete(key);\n if (typeof window !== 'undefined' && window.location.href === url.href) {\n window.history.replaceState(window.history.state, '', clean.href);\n }\n return clean.href;\n}\n","import { ApiError, createClient, type Client, type ClientOptions } from '../client';\nimport { authorization, createSessionFromCode, deleteSession, refreshSession } from '../api/auth/auth';\nimport type { AccountRead, PrincipalRead, SessionRead } from '../models';\nimport { AuthError, callbackAddress, challenge, cleanCallback, localReturnTo, parseCallback, randomSecret, transactionLifetime, transactionStore } from './transactions';\n\nexport interface MozaikAuthOptions {\n baseUrl: string;\n redirectUri: string;\n /** Only PKCE transactions are persisted. Access/refresh tokens stay in memory. */\n transactionStorage?: Storage;\n fetch?: ClientOptions['fetch'];\n}\nexport interface SsoOptions {\n returnTo?: string;\n prompt?: 'login' | 'none' | 'consent';\n}\nexport interface PreparedSso { url: string; provider: string | null; expiresAt: number }\nexport interface CompletedSso { account: AccountRead; principal: PrincipalRead; returnTo: string }\nexport interface AuthSnapshot {\n status: 'anonymous' | 'authenticating' | 'authenticated' | 'refreshing';\n isAuthenticated: boolean;\n isLoading: boolean;\n account: AccountRead | null;\n principal: PrincipalRead | null;\n error: Error | null;\n /** Stable across refreshes; replaced on login/logout to isolate account caches. */\n client: Client;\n}\nexport interface MozaikAuth {\n readonly client: Client;\n getSnapshot(): AuthSnapshot;\n subscribe(listener: () => void): () => void;\n prepareInternalSso(options?: SsoOptions): Promise<PreparedSso>;\n prepareProviderSso(alias: string, options?: SsoOptions): Promise<PreparedSso>;\n prepareSso(options?: SsoOptions): Promise<PreparedSso>;\n completeSso(callbackUrl?: string): Promise<CompletedSso>;\n refresh(): Promise<void>;\n logout(): Promise<void>;\n /** Local teardown only. Use logout() to revoke the server session. */\n dispose(): void;\n}\n\ninterface Session { data: SessionRead; expiresAt: number; refreshAt: number; refreshExpiresAt?: number }\nconst toError = (value: unknown) => value instanceof Error ? value : new AuthError('request_failed', 'Authentication request failed');\nconst changed = () => new AuthError('session_changed', 'Authentication session changed');\n\n/** Browser SSO through Mozaik's account-provisioning backend, with no client secret. */\nexport function createMozaikAuth(options: MozaikAuthOptions): MozaikAuth {\n const redirect = callbackAddress(options.redirectUri);\n const publicClient = createClient({ baseUrl: options.baseUrl, fetch: options.fetch });\n const transactions = transactionStore(publicClient.baseUrl, redirect, options.transactionStorage);\n const listeners = new Set<() => void>();\n const pending = new Set<symbol>();\n const callbacks = new Map<string, { url: string; cleanUrl: string; promise: Promise<CompletedSso> }>();\n let completed: { epoch: number; url: string; result: CompletedSso } | undefined;\n let disposed = false, epoch = 0;\n let session: Session | undefined;\n let error: Error | null = null;\n let abort = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n let flight: { epoch: number; response: Promise<SessionRead>; applied: Promise<void>; abort: AbortController } | undefined;\n let client = makeClient();\n let snapshot = makeSnapshot();\n\n function assertUsable() { if (disposed) throw new AuthError('disposed', 'Authentication client was disposed'); }\n function assertEpoch(expected: number) { assertUsable(); if (epoch !== expected) throw changed(); }\n function makeSnapshot(): AuthSnapshot {\n return Object.freeze({\n status: pending.size ? 'authenticating' : flight?.epoch === epoch ? 'refreshing' : session ? 'authenticated' : 'anonymous',\n isAuthenticated: Boolean(session), isLoading: pending.size > 0 || flight?.epoch === epoch,\n account: session?.data.account ?? null, principal: session?.data.principal ?? null, error, client,\n });\n }\n function publish() { snapshot = makeSnapshot(); for (const listener of listeners) listener(); }\n function clearSession() {\n epoch++;\n abort.abort(); abort = new AbortController();\n clearTimeout(timer); timer = undefined;\n session = undefined; pending.clear(); flight = undefined; completed = undefined;\n client = makeClient();\n }\n function makeClient(): Client {\n const boundEpoch = epoch, boundSignal = abort.signal;\n return createClient({\n baseUrl: publicClient.baseUrl,\n getAccessToken: async () => {\n assertEpoch(boundEpoch);\n if (session && Date.now() >= session.refreshAt) {\n try { await refresh(); }\n catch (failure) {\n // A transient early-refresh failure can still use a valid token.\n if (!session || Date.now() >= session.expiresAt) throw failure;\n }\n }\n assertEpoch(boundEpoch);\n return session?.data.access_token;\n },\n fetch: async (url, init) => {\n assertEpoch(boundEpoch);\n const signal = init?.signal ? AbortSignal.any([init.signal, boundSignal]) : boundSignal;\n signal.throwIfAborted();\n const response = await (options.fetch ?? globalThis.fetch)(url, { ...init, signal });\n assertEpoch(boundEpoch);\n return response;\n },\n });\n }\n function validateSession(data: SessionRead): void {\n if (!data || typeof data.access_token !== 'string' || !data.access_token ||\n !Number.isFinite(data.expires_in) || data.expires_in <= 0 ||\n !data.account || !data.principal || typeof data.principal.subject !== 'string' ||\n (data.refresh_token != null && (typeof data.refresh_token !== 'string' || !data.refresh_token))) {\n throw new AuthError('invalid_response', 'Invalid authentication session response');\n }\n }\n function schedule(delay: number) {\n clearTimeout(timer);\n timer = setTimeout(() => { void refresh().catch(() => {}); }, Math.min(2_147_483_647, Math.max(0, delay)));\n // Native/server consumers must be able to terminate without an auth timer keeping Node alive.\n (timer as unknown as { unref?: () => void }).unref?.();\n }\n function install(data: SessionRead) {\n validateSession(data);\n const now = Date.now(), ttl = data.expires_in * 1_000;\n session = {\n data, expiresAt: now + ttl,\n refreshAt: now + ttl - (data.refresh_token ? Math.min(30_000, ttl / 2) : 0),\n refreshExpiresAt: data.refresh_expires_in && data.refresh_expires_in > 0 ? now + data.refresh_expires_in * 1_000 : undefined,\n };\n error = null;\n schedule(session.refreshAt - now);\n }\n async function revoke(token?: string | null) {\n if (token) await deleteSession({ refresh_token: token }, { client: publicClient, signal: AbortSignal.timeout(20_000) });\n }\n\n function refresh(): Promise<void> {\n assertUsable();\n if (flight?.epoch === epoch) return flight.applied;\n const current = session, expected = epoch;\n if (!current) return Promise.reject(new AuthError('session_expired', 'Sign in before refreshing a session'));\n if (!current.data.refresh_token || (current.refreshExpiresAt && Date.now() >= current.refreshExpiresAt)) {\n const expired = new AuthError('session_expired', 'Session expired; sign in again');\n clearSession(); error = expired; publish();\n return Promise.reject(expired);\n }\n const requestAbort = new AbortController();\n const response = refreshSession({ refresh_token: current.data.refresh_token }, {\n client: publicClient, signal: AbortSignal.any([requestAbort.signal, AbortSignal.timeout(20_000)]),\n });\n const running = { epoch: expected, response, applied: Promise.resolve(), abort: requestAbort };\n flight = running;\n running.applied = response.then(data => {\n assertEpoch(expected);\n validateSession(data);\n if (data.principal.subject !== current.data.principal.subject || data.account.id !== current.data.account.id) {\n throw new AuthError('invalid_response', 'Refresh changed the authenticated account');\n }\n install(data);\n }).catch(failure => {\n if (!disposed && expected === epoch) {\n error = toError(failure);\n if ((failure instanceof ApiError && [401, 403].includes(failure.status)) ||\n failure instanceof AuthError || Date.now() >= current.expiresAt) {\n clearSession();\n } else {\n current.refreshAt = Math.min(current.expiresAt, Date.now() + 5_000);\n schedule(current.refreshAt - Date.now());\n }\n }\n throw failure;\n }).finally(() => {\n if (flight === running) flight = undefined;\n if (!disposed && expected === epoch) publish();\n else if (!disposed && !session) publish();\n });\n publish();\n return running.applied;\n }\n\n async function prepare(provider: string | undefined, settings: SsoOptions = {}): Promise<PreparedSso> {\n assertUsable();\n if (provider !== undefined && (!provider.trim() || provider.length > 64)) throw new TypeError('A registered provider alias is required');\n const returnTo = localReturnTo(settings.returnTo ?? '/', redirect.origin);\n const expected = epoch, operation = Symbol();\n pending.add(operation); error = null; publish();\n try {\n const state = randomSecret(), verifier = randomSecret(), codeChallenge = await challenge(verifier);\n assertEpoch(expected);\n const result = await authorization({\n redirect_uri: redirect.href, code_challenge: codeChallenge, state, provider, prompt: settings.prompt,\n }, { client: publicClient, signal: AbortSignal.any([abort.signal, AbortSignal.timeout(20_000)]) });\n assertEpoch(expected);\n const url = new URL(result.authorizationUrl);\n const suffix = '/protocol/openid-connect/auth';\n if (!['https:', 'http:'].includes(url.protocol) || url.username || url.password || url.hash || !url.pathname.endsWith(suffix) ||\n ['state', 'code_challenge', 'code_challenge_method', 'redirect_uri', 'response_type'].some(key => url.searchParams.getAll(key).length !== 1) ||\n url.searchParams.get('state') !== state || url.searchParams.get('code_challenge') !== codeChallenge ||\n url.searchParams.get('code_challenge_method') !== 'S256' || url.searchParams.get('redirect_uri') !== redirect.href ||\n url.searchParams.get('response_type') !== 'code' || (provider === 'google' && result.provider !== 'google')) {\n throw new AuthError('invalid_response', 'Invalid SSO authorization response');\n }\n const createdAt = Date.now();\n transactions.save(state, { verifier, createdAt, returnTo, provider: result.provider ?? null, issuer: url.origin + url.pathname.slice(0, -suffix.length) });\n return { url: url.href, provider: result.provider ?? null, expiresAt: createdAt + transactionLifetime };\n } catch (failure) {\n if (expected === epoch && !disposed) error = toError(failure);\n throw failure;\n } finally {\n pending.delete(operation);\n if (expected === epoch && !disposed) publish();\n }\n }\n\n function completeSso(callbackUrl?: string): Promise<CompletedSso> {\n try {\n assertUsable();\n const url = parseCallback(callbackUrl ?? (typeof window === 'undefined' ? '' : window.location.href), redirect);\n const state = url.searchParams.get('state') ?? '';\n // Strict Mode can call the default callback again after the first call\n // removed its OAuth parameters, but before the exchange has completed.\n if (callbackUrl === undefined && !state) {\n const pendingCallback = [...callbacks.values()].find(item => item.cleanUrl === url.href);\n if (pendingCallback) return pendingCallback.promise;\n if (completed?.epoch === epoch && completed.url === url.href) return Promise.resolve(completed.result);\n }\n const existing = callbacks.get(state);\n if (existing) {\n if (existing.url !== url.href) throw new AuthError('invalid_callback', 'A different callback is already using this state');\n return existing.promise;\n }\n const cleanUrl = cleanCallback(url);\n const transaction = transactions.consume(state);\n if (url.searchParams.has('iss') && url.searchParams.get('iss') !== transaction.issuer) throw new AuthError('invalid_issuer', 'Unexpected SSO callback issuer');\n if (url.searchParams.has('error')) throw new AuthError('provider_error', 'Sign-in was refused or cancelled by the identity provider');\n const code = url.searchParams.get('code');\n if (!code || code.length > 4096) throw new AuthError('invalid_callback', 'SSO callback is missing a valid authorization code');\n if (session || flight || callbacks.size || pending.size) clearSession();\n const expected = epoch, operation = Symbol();\n pending.add(operation); error = null; publish();\n const completion = (async () => {\n try {\n const data = await createSessionFromCode({ code, codeVerifier: transaction.verifier, redirectUri: redirect.href, state }, {\n client: publicClient, signal: AbortSignal.timeout(20_000),\n });\n if (disposed || expected !== epoch) { await revoke(data.refresh_token).catch(() => {}); throw changed(); }\n validateSession(data);\n if (transaction.provider && data.principal.provider !== transaction.provider) {\n await revoke(data.refresh_token).catch(() => {});\n throw new AuthError('provider_mismatch', 'Sign-in completed with a different identity provider');\n }\n // Anonymous requests made during the callback cannot inherit the new identity.\n clearSession(); install(data);\n const result = { account: data.account, principal: data.principal, returnTo: transaction.returnTo };\n completed = { epoch, url: cleanUrl, result };\n publish();\n return result;\n } catch (failure) {\n if (!disposed && expected === epoch) { error = toError(failure); publish(); }\n throw failure;\n } finally {\n callbacks.delete(state); pending.delete(operation);\n if (!disposed) publish();\n }\n })();\n callbacks.set(state, { url: url.href, cleanUrl, promise: completion });\n return completion;\n } catch (failure) {\n if (!disposed) { error = toError(failure); publish(); }\n return Promise.reject(failure);\n }\n }\n\n return {\n get client() { return client; }, getSnapshot: () => snapshot,\n subscribe(listener) { listeners.add(listener); return () => { listeners.delete(listener); }; },\n prepareInternalSso: settings => prepare('google', settings),\n prepareProviderSso: (alias, settings) => prepare(alias, settings),\n prepareSso: settings => prepare(undefined, settings), completeSso, refresh,\n async logout() {\n assertUsable();\n const old = session, rotating = flight;\n clearSession(); error = null; publish();\n const expected = epoch;\n // Local logout is immediate, even if storage or the revocation request fails.\n let cleanupError: unknown;\n try { transactions.clear(); } catch (failure) { cleanupError = failure; }\n try {\n // A refresh already sent may rotate the token. Revoke its result, not an\n // obsolete token, without ever restoring it to the local session.\n const rotated = await rotating?.response.catch(() => undefined);\n await revoke(rotated?.refresh_token ?? old?.data.refresh_token);\n if (cleanupError) throw cleanupError;\n } catch (failure) {\n if (!disposed && expected === epoch) { error = toError(failure); publish(); }\n throw failure;\n }\n },\n dispose() {\n if (disposed) return;\n flight?.abort.abort();\n clearSession(); disposed = true; error = null; publish(); listeners.clear();\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/chat.d.ts
ADDED
package/dist/chat.js
ADDED
|
@@ -25,4 +25,4 @@ type ErrorType<T> = ApiError<T>;
|
|
|
25
25
|
type BodyType<T> = T;
|
|
26
26
|
//#endregion
|
|
27
27
|
export { ErrorType as a, request as c, ClientOptions as i, BodyType as n, RequestOptions as o, Client as r, createClient as s, ApiError as t };
|
|
28
|
-
//# sourceMappingURL=client
|
|
28
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../../src/client.ts"],"sourcesContent":["/** Configuration belongs to a client instance, never to global authentication state. */\nexport interface ClientOptions {\n baseUrl: string;\n getAccessToken?: () => string | null | undefined | Promise<string | null | undefined>;\n headers?: HeadersInit;\n fetch?: typeof globalThis.fetch;\n}\n\nexport interface Client extends Readonly<ClientOptions> {}\n\nexport interface RequestOptions extends RequestInit {\n client?: Client;\n}\n\nexport class ApiError<T = unknown> extends Error {\n readonly status: number;\n readonly data: T;\n readonly headers: Headers;\n\n constructor(response: Response, data: T) {\n super(`Mozaik API returned HTTP ${response.status}`);\n this.name = 'ApiError';\n this.status = response.status;\n this.data = data;\n this.headers = response.headers;\n }\n}\n\nexport function createClient(options: ClientOptions): Client {\n const url = new URL(options.baseUrl);\n if (!['https:', 'http:'].includes(url.protocol) || url.username || url.password || url.search || url.hash || url.pathname !== '/') {\n throw new TypeError('baseUrl must be an HTTP(S) origin without credentials, path, query or fragment');\n }\n return Object.freeze({ ...options, baseUrl: url.origin, headers: new Headers(options.headers) });\n}\n\n/** Orval transport: typed successful data, rejected HTTP errors, native cancellation. */\nexport async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {\n const { client, headers: requestHeaders, ...init } = options;\n const headers = new Headers(client?.headers);\n new Headers(requestHeaders).forEach((value, key) => headers.set(key, value));\n const url = client ? new URL(path, client.baseUrl) : undefined;\n if (client && url?.origin !== client.baseUrl) {\n throw new TypeError('Refusing to send client credentials to another origin');\n }\n if (!headers.has('Authorization')) {\n const token = await client?.getAccessToken?.();\n if (token) headers.set('Authorization', `Bearer ${token}`);\n }\n if (init.body instanceof FormData) headers.delete('Content-Type');\n if (!headers.has('Accept')) headers.set('Accept', 'application/json');\n const response = await (client?.fetch ?? globalThis.fetch)(url?.href ?? path, {\n credentials: 'omit', ...init, headers,\n });\n let data: unknown;\n if (response.status !== 204 && response.status !== 205 && init.method !== 'HEAD') {\n const text = await response.text();\n if (text) {\n if (response.headers.get('Content-Type')?.includes('json')) {\n try { data = JSON.parse(text); }\n catch (error) { if (response.ok) throw error; data = text; }\n } else data = text;\n }\n }\n if (!response.ok) throw new ApiError(response, data);\n return data as T;\n}\n\n/** Used by generated React Query hooks for HTTP failures. */\nexport type ErrorType<T> = ApiError<T>;\nexport type BodyType<T> = T;\n"],"mappings":";;AAcA,IAAa,WAAb,cAA2C,MAAM;CAC/C;CACA;CACA;CAEA,YAAY,UAAoB,MAAS;EACvC,MAAM,4BAA4B,SAAS,QAAQ;EACnD,KAAK,OAAO;EACZ,KAAK,SAAS,SAAS;EACvB,KAAK,OAAO;EACZ,KAAK,UAAU,SAAS;CAC1B;AACF;AAEA,SAAgB,aAAa,SAAgC;CAC3D,MAAM,MAAM,IAAI,IAAI,QAAQ,OAAO;CACnC,IAAI,CAAC,CAAC,UAAU,OAAO,CAAC,CAAC,SAAS,IAAI,QAAQ,KAAK,IAAI,YAAY,IAAI,YAAY,IAAI,UAAU,IAAI,QAAQ,IAAI,aAAa,KAC5H,MAAM,IAAI,UAAU,gFAAgF;CAEtG,OAAO,OAAO,OAAO;EAAE,GAAG;EAAS,SAAS,IAAI;EAAQ,SAAS,IAAI,QAAQ,QAAQ,OAAO;CAAE,CAAC;AACjG;;AAGA,eAAsB,QAAW,MAAc,UAA0B,CAAC,GAAe;CACvF,MAAM,EAAE,QAAQ,SAAS,gBAAgB,GAAG,SAAS;CACrD,MAAM,UAAU,IAAI,QAAQ,QAAQ,OAAO;CAC3C,IAAI,QAAQ,cAAc,CAAC,CAAC,SAAS,OAAO,QAAQ,QAAQ,IAAI,KAAK,KAAK,CAAC;CAC3E,MAAM,MAAM,SAAS,IAAI,IAAI,MAAM,OAAO,OAAO,IAAI,KAAA;CACrD,IAAI,UAAU,KAAK,WAAW,OAAO,SACnC,MAAM,IAAI,UAAU,uDAAuD;CAE7E,IAAI,CAAC,QAAQ,IAAI,eAAe,GAAG;EACjC,MAAM,QAAQ,MAAM,QAAQ,iBAAiB;EAC7C,IAAI,OAAO,QAAQ,IAAI,iBAAiB,UAAU,OAAO;CAC3D;CACA,IAAI,KAAK,gBAAgB,UAAU,QAAQ,OAAO,cAAc;CAChE,IAAI,CAAC,QAAQ,IAAI,QAAQ,GAAG,QAAQ,IAAI,UAAU,kBAAkB;CACpE,MAAM,WAAW,OAAO,QAAQ,SAAS,WAAW,MAAA,CAAO,KAAK,QAAQ,MAAM;EAC5E,aAAa;EAAQ,GAAG;EAAM;CAChC,CAAC;CACD,IAAI;CACJ,IAAI,SAAS,WAAW,OAAO,SAAS,WAAW,OAAO,KAAK,WAAW,QAAQ;EAChF,MAAM,OAAO,MAAM,SAAS,KAAK;EACjC,IAAI,MAAM;GACR,IAAI,SAAS,QAAQ,IAAI,cAAc,CAAC,EAAE,SAAS,MAAM,GACvD,IAAI;IAAE,OAAO,KAAK,MAAM,IAAI;GAAG,SACxB,OAAO;IAAE,IAAI,SAAS,IAAI,MAAM;IAAO,OAAO;GAAM;QACtD,OAAO;EAChB;CACF;CACA,IAAI,CAAC,SAAS,IAAI,MAAM,IAAI,SAAS,UAAU,IAAI;CACnD,OAAO;AACT"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
|
|
2
|
+
import { Ri as AccountRead, mn as PrincipalRead } from "./index.js";
|
|
3
|
+
import { i as ClientOptions, r as Client } from "./client.js";
|
|
4
|
+
//#region src/auth/client.d.ts
|
|
5
|
+
interface MozaikAuthOptions {
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
redirectUri: string;
|
|
8
|
+
/** Only PKCE transactions are persisted. Access/refresh tokens stay in memory. */
|
|
9
|
+
transactionStorage?: Storage;
|
|
10
|
+
fetch?: ClientOptions['fetch'];
|
|
11
|
+
}
|
|
12
|
+
interface SsoOptions {
|
|
13
|
+
returnTo?: string;
|
|
14
|
+
prompt?: 'login' | 'none' | 'consent';
|
|
15
|
+
}
|
|
16
|
+
interface PreparedSso {
|
|
17
|
+
url: string;
|
|
18
|
+
provider: string | null;
|
|
19
|
+
expiresAt: number;
|
|
20
|
+
}
|
|
21
|
+
interface CompletedSso {
|
|
22
|
+
account: AccountRead;
|
|
23
|
+
principal: PrincipalRead;
|
|
24
|
+
returnTo: string;
|
|
25
|
+
}
|
|
26
|
+
interface AuthSnapshot {
|
|
27
|
+
status: 'anonymous' | 'authenticating' | 'authenticated' | 'refreshing';
|
|
28
|
+
isAuthenticated: boolean;
|
|
29
|
+
isLoading: boolean;
|
|
30
|
+
account: AccountRead | null;
|
|
31
|
+
principal: PrincipalRead | null;
|
|
32
|
+
error: Error | null;
|
|
33
|
+
/** Stable across refreshes; replaced on login/logout to isolate account caches. */
|
|
34
|
+
client: Client;
|
|
35
|
+
}
|
|
36
|
+
interface MozaikAuth {
|
|
37
|
+
readonly client: Client;
|
|
38
|
+
getSnapshot(): AuthSnapshot;
|
|
39
|
+
subscribe(listener: () => void): () => void;
|
|
40
|
+
prepareInternalSso(options?: SsoOptions): Promise<PreparedSso>;
|
|
41
|
+
prepareProviderSso(alias: string, options?: SsoOptions): Promise<PreparedSso>;
|
|
42
|
+
prepareSso(options?: SsoOptions): Promise<PreparedSso>;
|
|
43
|
+
completeSso(callbackUrl?: string): Promise<CompletedSso>;
|
|
44
|
+
refresh(): Promise<void>;
|
|
45
|
+
logout(): Promise<void>;
|
|
46
|
+
/** Local teardown only. Use logout() to revoke the server session. */
|
|
47
|
+
dispose(): void;
|
|
48
|
+
}
|
|
49
|
+
/** Browser SSO through Mozaik's account-provisioning backend, with no client secret. */
|
|
50
|
+
declare function createMozaikAuth(options: MozaikAuthOptions): MozaikAuth;
|
|
51
|
+
//#endregion
|
|
52
|
+
export { PreparedSso as a, MozaikAuthOptions as i, CompletedSso as n, SsoOptions as o, MozaikAuth as r, createMozaikAuth as s, AuthSnapshot as t };
|
|
53
|
+
//# sourceMappingURL=client2.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
import { dn as MessageRead } from "./index.js";
|
|
3
|
+
import { r as Client } from "./client.js";
|
|
4
|
+
//#region src/chat/connection.d.ts
|
|
5
|
+
type ChatStatus = 'connecting' | 'syncing' | 'connected' | 'reconnecting' | 'error' | 'closed';
|
|
6
|
+
interface ChatConnectionOptions {
|
|
7
|
+
client: Client;
|
|
8
|
+
requestId: number;
|
|
9
|
+
onMessage: (message: MessageRead) => void;
|
|
10
|
+
/** Runs after subscription readiness, with live messages buffered until it resolves. */
|
|
11
|
+
onConnect?: (signal: AbortSignal) => Promise<void>;
|
|
12
|
+
onStatusChange?: (status: ChatStatus) => void;
|
|
13
|
+
onError?: (error: Error) => void;
|
|
14
|
+
/** Defaults to the browser's native WebSocket. Also useful for native clients/tests. */
|
|
15
|
+
webSocketFactory?: (url: string) => WebSocket;
|
|
16
|
+
/** Attempt starts at zero; the default is exponential backoff with jitter, capped at 30s. */
|
|
17
|
+
retryDelay?: (attempt: number) => number;
|
|
18
|
+
connectTimeoutMs?: number;
|
|
19
|
+
}
|
|
20
|
+
interface ChatConnection {
|
|
21
|
+
/** Cancels ticket/history requests, reconnect timers and the socket. Safe to call twice. */
|
|
22
|
+
close(): void;
|
|
23
|
+
}
|
|
24
|
+
/** Open an authenticated live feed. Use generated REST methods to send and read history. */
|
|
25
|
+
declare function connectTutoringChat(options: ChatConnectionOptions): ChatConnection;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { connectTutoringChat as i, ChatConnectionOptions as n, ChatStatus as r, ChatConnection as t };
|
|
28
|
+
//# sourceMappingURL=connection.d.ts.map
|