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.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 readSetCookie(response) {
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 match = /(?:^|,\s*)vm_session=([^;]+)/i.exec(entry);
205
- if (match?.[1]) return match[1];
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.Cookie = `vm_session=${tokens.sessionCookie}`;
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 = readSetCookie(response);
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.tokens = {
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 = 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
- this.clearSession();
919
- throw new SabhaError(
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, and ensure the `vm_session` cookie is sent (credentials: 'include', CORS allow-credentials, allow-listed Origin).",
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(client.getStatus().authenticated);
1145
- setUser(client.getUser());
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
- void client.me().then((me) => {
1310
+ let cancelled = false;
1311
+ void client.me().then(async (me) => {
1312
+ if (cancelled) return;
1189
1313
  setUser(me);
1190
1314
  setAuthenticated(true);
1191
- return refreshMeetings();
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