sabha-meetings-react 0.1.4 → 0.1.7
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/dist/index.d.mts +23 -4
- package/dist/index.d.ts +23 -4
- package/dist/index.js +177 -34
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +171 -35
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -161,6 +161,83 @@ function toSabhaError(error, fallback = "Sabha request failed") {
|
|
|
161
161
|
return new SabhaError(fallback, { details: error });
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
// src/session.ts
|
|
165
|
+
var SABHA_SESSION_HEADER = "x-sabha-session";
|
|
166
|
+
var SABHA_SESSION_STORAGE_KEY = "sabha_vm_session";
|
|
167
|
+
var SABHA_TOKENS_STORAGE_KEY = "sabha_auth_tokens";
|
|
168
|
+
function readJson(key) {
|
|
169
|
+
try {
|
|
170
|
+
if (typeof localStorage === "undefined") return null;
|
|
171
|
+
const raw = localStorage.getItem(key);
|
|
172
|
+
if (!raw) return null;
|
|
173
|
+
return JSON.parse(raw);
|
|
174
|
+
} catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function writeJson(key, value) {
|
|
179
|
+
try {
|
|
180
|
+
if (typeof localStorage === "undefined") return;
|
|
181
|
+
if (value == null) localStorage.removeItem(key);
|
|
182
|
+
else localStorage.setItem(key, JSON.stringify(value));
|
|
183
|
+
} catch {
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function readStoredTokens() {
|
|
187
|
+
const parsed = readJson(SABHA_TOKENS_STORAGE_KEY);
|
|
188
|
+
if (parsed && typeof parsed === "object") {
|
|
189
|
+
const record = parsed;
|
|
190
|
+
const tokens = {
|
|
191
|
+
accessToken: typeof record.accessToken === "string" ? record.accessToken : void 0,
|
|
192
|
+
refreshToken: typeof record.refreshToken === "string" ? record.refreshToken : void 0,
|
|
193
|
+
sessionCookie: typeof record.sessionCookie === "string" ? record.sessionCookie : void 0
|
|
194
|
+
};
|
|
195
|
+
if (tokens.accessToken || tokens.refreshToken || tokens.sessionCookie) {
|
|
196
|
+
return tokens;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const legacy = readStoredSessionCookie();
|
|
200
|
+
if (legacy) {
|
|
201
|
+
const migrated = { sessionCookie: legacy };
|
|
202
|
+
writeStoredTokens(migrated);
|
|
203
|
+
return migrated;
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
function writeStoredTokens(tokens) {
|
|
208
|
+
if (!tokens || !tokens.accessToken && !tokens.refreshToken && !tokens.sessionCookie) {
|
|
209
|
+
writeJson(SABHA_TOKENS_STORAGE_KEY, null);
|
|
210
|
+
writeStoredSessionCookie(null);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
writeJson(SABHA_TOKENS_STORAGE_KEY, {
|
|
214
|
+
accessToken: tokens.accessToken,
|
|
215
|
+
refreshToken: tokens.refreshToken,
|
|
216
|
+
sessionCookie: tokens.sessionCookie
|
|
217
|
+
});
|
|
218
|
+
writeStoredSessionCookie(tokens.sessionCookie ?? null);
|
|
219
|
+
}
|
|
220
|
+
function readStoredSessionCookie() {
|
|
221
|
+
try {
|
|
222
|
+
if (typeof localStorage === "undefined") return null;
|
|
223
|
+
return localStorage.getItem(SABHA_SESSION_STORAGE_KEY);
|
|
224
|
+
} catch {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function writeStoredSessionCookie(value) {
|
|
229
|
+
try {
|
|
230
|
+
if (typeof localStorage === "undefined") return;
|
|
231
|
+
if (!value) localStorage.removeItem(SABHA_SESSION_STORAGE_KEY);
|
|
232
|
+
else localStorage.setItem(SABHA_SESSION_STORAGE_KEY, value);
|
|
233
|
+
} catch {
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function parseVmSession(cookieHeader) {
|
|
237
|
+
const match = /(?:^|,\s*)vm_session=([^;]+)/i.exec(cookieHeader);
|
|
238
|
+
return match?.[1] ? decodeURIComponent(match[1].trim()) : null;
|
|
239
|
+
}
|
|
240
|
+
|
|
164
241
|
// src/http/client.ts
|
|
165
242
|
function joinUrl(baseUrl, path) {
|
|
166
243
|
const base = baseUrl.replace(/\/+$/, "");
|
|
@@ -194,15 +271,21 @@ function messageFromBody(body, fallback) {
|
|
|
194
271
|
if (typeof record.error === "string") return record.error;
|
|
195
272
|
return fallback;
|
|
196
273
|
}
|
|
197
|
-
function
|
|
274
|
+
function isBrowser() {
|
|
275
|
+
const runtime = globalThis;
|
|
276
|
+
return typeof runtime.window !== "undefined" && typeof runtime.document !== "undefined";
|
|
277
|
+
}
|
|
278
|
+
function readSessionFromResponse(response) {
|
|
279
|
+
const bridged = response.headers.get(SABHA_SESSION_HEADER);
|
|
280
|
+
if (bridged?.trim()) return bridged.trim();
|
|
198
281
|
const anyHeaders = response.headers;
|
|
199
282
|
const list = typeof anyHeaders.getSetCookie === "function" ? anyHeaders.getSetCookie() : (() => {
|
|
200
283
|
const single = response.headers.get("set-cookie");
|
|
201
284
|
return single ? [single] : [];
|
|
202
285
|
})();
|
|
203
286
|
for (const entry of list) {
|
|
204
|
-
const
|
|
205
|
-
if (
|
|
287
|
+
const value = parseVmSession(entry);
|
|
288
|
+
if (value) return value;
|
|
206
289
|
}
|
|
207
290
|
return null;
|
|
208
291
|
}
|
|
@@ -216,10 +299,8 @@ var HttpClient = class {
|
|
|
216
299
|
Accept: "application/json",
|
|
217
300
|
...options.headers
|
|
218
301
|
};
|
|
219
|
-
const runtime = globalThis;
|
|
220
|
-
const isBrowser = typeof runtime.window !== "undefined" && typeof runtime.document !== "undefined";
|
|
221
302
|
const origin = this.options.getOrigin();
|
|
222
|
-
if (!isBrowser && origin) headers.Origin = origin;
|
|
303
|
+
if (!isBrowser() && origin) headers.Origin = origin;
|
|
223
304
|
const tokens = this.options.getTokens();
|
|
224
305
|
const needsAuth = options.auth !== false;
|
|
225
306
|
if (tokens?.accessToken) {
|
|
@@ -231,7 +312,10 @@ var HttpClient = class {
|
|
|
231
312
|
);
|
|
232
313
|
}
|
|
233
314
|
if (tokens?.sessionCookie) {
|
|
234
|
-
headers
|
|
315
|
+
headers[SABHA_SESSION_HEADER] = tokens.sessionCookie;
|
|
316
|
+
if (!isBrowser()) {
|
|
317
|
+
headers.Cookie = `vm_session=${tokens.sessionCookie}`;
|
|
318
|
+
}
|
|
235
319
|
}
|
|
236
320
|
if (options.body !== void 0) {
|
|
237
321
|
headers["Content-Type"] = "application/json";
|
|
@@ -245,7 +329,7 @@ var HttpClient = class {
|
|
|
245
329
|
credentials: this.options.withCredentials ? "include" : "same-origin"
|
|
246
330
|
}
|
|
247
331
|
);
|
|
248
|
-
const session =
|
|
332
|
+
const session = readSessionFromResponse(response);
|
|
249
333
|
if (session) this.options.onSessionCookie?.(session);
|
|
250
334
|
const body = await parseBody(response);
|
|
251
335
|
if (response.status === 401 && needsAuth && !retried && this.options.onUnauthorized) {
|
|
@@ -830,6 +914,7 @@ var SabhaClient = class {
|
|
|
830
914
|
);
|
|
831
915
|
this.config = {
|
|
832
916
|
withCredentials: true,
|
|
917
|
+
persistSession: true,
|
|
833
918
|
...this.config,
|
|
834
919
|
...config,
|
|
835
920
|
baseUrl
|
|
@@ -838,18 +923,19 @@ var SabhaClient = class {
|
|
|
838
923
|
...DEFAULT_MEDIA_POLICY,
|
|
839
924
|
...this.config.mediaPolicy
|
|
840
925
|
};
|
|
926
|
+
const stored = this.config.persistSession !== false ? readStoredTokens() : null;
|
|
841
927
|
this.tokens = {
|
|
842
|
-
accessToken: config.accessToken ?? this.tokens?.accessToken,
|
|
843
|
-
refreshToken: config.refreshToken ?? this.tokens?.refreshToken,
|
|
844
|
-
sessionCookie: config.sessionCookie ?? this.tokens?.sessionCookie
|
|
928
|
+
accessToken: config.accessToken ?? this.tokens?.accessToken ?? stored?.accessToken,
|
|
929
|
+
refreshToken: config.refreshToken ?? this.tokens?.refreshToken ?? stored?.refreshToken,
|
|
930
|
+
sessionCookie: config.sessionCookie ?? this.tokens?.sessionCookie ?? stored?.sessionCookie ?? void 0
|
|
845
931
|
};
|
|
846
|
-
if (!this.tokens.accessToken && !this.tokens.sessionCookie && !this.tokens.refreshToken) {
|
|
847
|
-
if (!config.accessToken && !config.sessionCookie && !config.refreshToken) ;
|
|
848
|
-
}
|
|
849
932
|
const hasAnyToken = Boolean(
|
|
850
933
|
this.tokens.accessToken || this.tokens.sessionCookie || this.tokens.refreshToken
|
|
851
934
|
);
|
|
852
935
|
if (!hasAnyToken) this.tokens = null;
|
|
936
|
+
else if (this.config.persistSession !== false) {
|
|
937
|
+
this.persistTokens(this.tokens);
|
|
938
|
+
}
|
|
853
939
|
this.http = new HttpClient({
|
|
854
940
|
baseUrl,
|
|
855
941
|
withCredentials: this.config.withCredentials !== false,
|
|
@@ -857,12 +943,12 @@ var SabhaClient = class {
|
|
|
857
943
|
getTokens: () => this.tokens,
|
|
858
944
|
setTokens: (tokens) => {
|
|
859
945
|
this.tokens = tokens;
|
|
946
|
+
if (this.config.persistSession !== false) {
|
|
947
|
+
this.persistTokens(tokens);
|
|
948
|
+
}
|
|
860
949
|
},
|
|
861
950
|
onSessionCookie: (cookie) => {
|
|
862
|
-
this.
|
|
863
|
-
...this.tokens ?? {},
|
|
864
|
-
sessionCookie: cookie ?? void 0
|
|
865
|
-
};
|
|
951
|
+
this.persistSessionCookie(cookie);
|
|
866
952
|
},
|
|
867
953
|
onUnauthorized: () => this.tryRefresh()
|
|
868
954
|
});
|
|
@@ -899,11 +985,12 @@ var SabhaClient = class {
|
|
|
899
985
|
}
|
|
900
986
|
setTokens(tokens) {
|
|
901
987
|
this.ensureConfigured();
|
|
902
|
-
this.tokens
|
|
988
|
+
this.persistTokens(tokens);
|
|
903
989
|
}
|
|
904
990
|
clearSession() {
|
|
905
991
|
this.tokens = null;
|
|
906
992
|
this.user = null;
|
|
993
|
+
this.persistTokens(null);
|
|
907
994
|
this.activeConnection?.disconnect();
|
|
908
995
|
this.activeConnection = null;
|
|
909
996
|
}
|
|
@@ -914,18 +1001,27 @@ var SabhaClient = class {
|
|
|
914
1001
|
const me = await this.auth().me();
|
|
915
1002
|
this.user = me;
|
|
916
1003
|
session.user = me;
|
|
1004
|
+
session.accessToken = this.tokens?.accessToken;
|
|
1005
|
+
session.refreshToken = this.tokens?.refreshToken;
|
|
1006
|
+
session.sessionCookie = this.tokens?.sessionCookie;
|
|
1007
|
+
return session;
|
|
917
1008
|
} catch (error) {
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
"Login returned a user profile but no auth credential (Postman contract). Sabha-API.postman_collection.json requires /api/auth/login to return `token` (stored as accessToken) for Bearer auth on POST /api/meetings. Live API currently returns neither `token` nor Set-Cookie vm_session, so meetings fail with 401 Sign in first. Fix sabha-api login to return JWT `token`/`refreshToken` (preferred) or set HttpOnly vm_session (SameSite=None; Secure).",
|
|
921
|
-
{
|
|
922
|
-
code: "SESSION_NOT_ESTABLISHED",
|
|
923
|
-
status: 401,
|
|
924
|
-
cause: error
|
|
925
|
-
}
|
|
1009
|
+
const hasCredential = Boolean(
|
|
1010
|
+
this.tokens?.accessToken || this.tokens?.sessionCookie
|
|
926
1011
|
);
|
|
1012
|
+
if (!hasCredential) {
|
|
1013
|
+
this.clearSession();
|
|
1014
|
+
throw new SabhaError(
|
|
1015
|
+
"Login returned a user profile but no auth credential. Need JWT `token` (+ optional `refreshToken`) from /api/auth/login as in Sabha-API Postman (Auth \u2192 Login). Without that, POST /api/meetings returns 401 Sign in first.",
|
|
1016
|
+
{
|
|
1017
|
+
code: "SESSION_NOT_ESTABLISHED",
|
|
1018
|
+
status: 401,
|
|
1019
|
+
cause: error
|
|
1020
|
+
}
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
throw error;
|
|
927
1024
|
}
|
|
928
|
-
return session;
|
|
929
1025
|
}
|
|
930
1026
|
/** Throws if /api/auth/me fails — call before create/start meeting. */
|
|
931
1027
|
async ensureAuthenticated() {
|
|
@@ -933,7 +1029,7 @@ var SabhaClient = class {
|
|
|
933
1029
|
return await this.me();
|
|
934
1030
|
} catch (error) {
|
|
935
1031
|
throw new SabhaError(
|
|
936
|
-
"Not signed in to Sabha. Call login() first
|
|
1032
|
+
"Not signed in to Sabha. Call login() first so Authorization: Bearer <accessToken> is sent (Postman Auth \u2192 Login stores json.token).",
|
|
937
1033
|
{
|
|
938
1034
|
code: "UNAUTHENTICATED",
|
|
939
1035
|
status: 401,
|
|
@@ -1055,13 +1151,36 @@ var SabhaClient = class {
|
|
|
1055
1151
|
refreshToken: session.refreshToken ?? this.tokens?.refreshToken,
|
|
1056
1152
|
sessionCookie: session.sessionCookie ?? this.tokens?.sessionCookie
|
|
1057
1153
|
};
|
|
1154
|
+
this.persistTokens(this.tokens);
|
|
1155
|
+
}
|
|
1156
|
+
persistTokens(tokens) {
|
|
1157
|
+
this.tokens = tokens;
|
|
1158
|
+
if (this.config.persistSession !== false) {
|
|
1159
|
+
writeStoredTokens(tokens);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
persistSessionCookie(cookie) {
|
|
1163
|
+
if (cookie) {
|
|
1164
|
+
this.persistTokens({
|
|
1165
|
+
...this.tokens ?? {},
|
|
1166
|
+
sessionCookie: cookie
|
|
1167
|
+
});
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
if (!this.tokens) {
|
|
1171
|
+
this.persistTokens(null);
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
const { sessionCookie: _removed, ...rest } = this.tokens;
|
|
1175
|
+
this.persistTokens(Object.keys(rest).length ? rest : null);
|
|
1058
1176
|
}
|
|
1059
1177
|
async tryRefresh() {
|
|
1060
1178
|
if (!this.tokens?.refreshToken) return false;
|
|
1061
1179
|
if (this.refreshPromise) return this.refreshPromise;
|
|
1062
1180
|
this.refreshPromise = (async () => {
|
|
1063
1181
|
try {
|
|
1064
|
-
await this.refreshSession();
|
|
1182
|
+
const tokens = await this.refreshSession();
|
|
1183
|
+
this.persistTokens({ ...this.tokens ?? {}, ...tokens });
|
|
1065
1184
|
return true;
|
|
1066
1185
|
} catch {
|
|
1067
1186
|
return false;
|
|
@@ -1117,6 +1236,7 @@ function SabhaProvider({
|
|
|
1117
1236
|
const resolvedConfig = {
|
|
1118
1237
|
baseUrl: DEFAULT_BASE_URL,
|
|
1119
1238
|
withCredentials: true,
|
|
1239
|
+
persistSession: true,
|
|
1120
1240
|
...config
|
|
1121
1241
|
};
|
|
1122
1242
|
const client = useMemo(
|
|
@@ -1141,8 +1261,10 @@ function SabhaProvider({
|
|
|
1141
1261
|
const [error, setError] = useState(null);
|
|
1142
1262
|
useEffect(() => {
|
|
1143
1263
|
client.configure(resolvedConfig);
|
|
1144
|
-
setAuthenticated(
|
|
1145
|
-
setUser(
|
|
1264
|
+
setAuthenticated(false);
|
|
1265
|
+
setUser(null);
|
|
1266
|
+
setError(null);
|
|
1267
|
+
setMeetings([]);
|
|
1146
1268
|
setReady(true);
|
|
1147
1269
|
}, [
|
|
1148
1270
|
client,
|
|
@@ -1185,12 +1307,26 @@ function SabhaProvider({
|
|
|
1185
1307
|
}, [client]);
|
|
1186
1308
|
useEffect(() => {
|
|
1187
1309
|
if (!ready || !autoLoadMeetings) return;
|
|
1188
|
-
|
|
1310
|
+
let cancelled = false;
|
|
1311
|
+
void client.me().then(async (me) => {
|
|
1312
|
+
if (cancelled) return;
|
|
1189
1313
|
setUser(me);
|
|
1190
1314
|
setAuthenticated(true);
|
|
1191
|
-
|
|
1315
|
+
try {
|
|
1316
|
+
await refreshMeetings();
|
|
1317
|
+
} catch {
|
|
1318
|
+
}
|
|
1192
1319
|
}).catch(() => {
|
|
1320
|
+
if (cancelled) return;
|
|
1321
|
+
client.clearSession();
|
|
1322
|
+
setAuthenticated(false);
|
|
1323
|
+
setUser(null);
|
|
1324
|
+
setMeetings([]);
|
|
1325
|
+
setError(null);
|
|
1193
1326
|
});
|
|
1327
|
+
return () => {
|
|
1328
|
+
cancelled = true;
|
|
1329
|
+
};
|
|
1194
1330
|
}, [ready, autoLoadMeetings, client, refreshMeetings]);
|
|
1195
1331
|
const createMeeting = useCallback(
|
|
1196
1332
|
async (input) => {
|
|
@@ -1449,6 +1585,6 @@ function useSabhaMeetings() {
|
|
|
1449
1585
|
};
|
|
1450
1586
|
}
|
|
1451
1587
|
|
|
1452
|
-
export { ContentProtection, DEFAULT_BASE_URL, DEFAULT_MEDIA_POLICY, MediaController, MeetingConnection, SabhaClient, SabhaError, SabhaProvider, createSabhaClient, normalizeMeeting, toSabhaError, useSabha, useSabhaMeetings };
|
|
1588
|
+
export { ContentProtection, DEFAULT_BASE_URL, DEFAULT_MEDIA_POLICY, MediaController, MeetingConnection, SABHA_SESSION_HEADER, SABHA_SESSION_STORAGE_KEY, SABHA_TOKENS_STORAGE_KEY, SabhaClient, SabhaError, SabhaProvider, createSabhaClient, normalizeMeeting, readStoredSessionCookie, readStoredTokens, toSabhaError, useSabha, useSabhaMeetings, writeStoredSessionCookie, writeStoredTokens };
|
|
1453
1589
|
//# sourceMappingURL=index.mjs.map
|
|
1454
1590
|
//# sourceMappingURL=index.mjs.map
|