@korajs/auth 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.cjs CHANGED
@@ -23,6 +23,8 @@ __export(react_exports, {
23
23
  AuthContext: () => AuthContext,
24
24
  AuthProvider: () => AuthProvider,
25
25
  OrgContext: () => OrgContext,
26
+ OrgProvider: () => OrgProvider,
27
+ checkOrgPermission: () => checkOrgPermission,
26
28
  useAuth: () => useAuth,
27
29
  useAuthStatus: () => useAuthStatus,
28
30
  useCurrentUser: () => useCurrentUser,
@@ -35,41 +37,132 @@ module.exports = __toCommonJS(react_exports);
35
37
  // src/react/AuthProvider.tsx
36
38
  var import_react2 = require("react");
37
39
 
40
+ // src/bindings/create-auth-session.ts
41
+ function createAuthSession(client) {
42
+ let state = client.state;
43
+ let isLoading = true;
44
+ let initError = null;
45
+ let lastError = null;
46
+ const listeners = /* @__PURE__ */ new Set();
47
+ let snapshot = buildSnapshot2();
48
+ const refreshSnapshot = () => {
49
+ snapshot = buildSnapshot2();
50
+ };
51
+ const notify = () => {
52
+ refreshSnapshot();
53
+ for (const listener of listeners) {
54
+ listener();
55
+ }
56
+ };
57
+ function buildSnapshot2() {
58
+ return {
59
+ state,
60
+ user: client.currentUser,
61
+ isAuthenticated: state === "authenticated",
62
+ isLoading,
63
+ initError,
64
+ error: lastError
65
+ };
66
+ }
67
+ const captureError = (error) => {
68
+ lastError = error instanceof Error ? error.message : String(error);
69
+ notify();
70
+ };
71
+ const run = async (action) => {
72
+ lastError = null;
73
+ try {
74
+ await action();
75
+ } catch (error) {
76
+ captureError(error);
77
+ }
78
+ };
79
+ const runWithResult = async (action) => {
80
+ lastError = null;
81
+ try {
82
+ return await action();
83
+ } catch (error) {
84
+ captureError(error);
85
+ return null;
86
+ }
87
+ };
88
+ const unsubscribeAuth = client.onAuthChange((nextState) => {
89
+ state = nextState;
90
+ notify();
91
+ });
92
+ void client.initialize().then(() => {
93
+ state = client.state;
94
+ isLoading = false;
95
+ notify();
96
+ }).catch((error) => {
97
+ initError = error instanceof Error ? error : new Error(String(error));
98
+ isLoading = false;
99
+ notify();
100
+ });
101
+ return {
102
+ client,
103
+ getSnapshot: () => snapshot,
104
+ subscribe(listener) {
105
+ listeners.add(listener);
106
+ return () => {
107
+ listeners.delete(listener);
108
+ };
109
+ },
110
+ signUp: (params) => run(async () => {
111
+ await client.signUp(params);
112
+ }),
113
+ signIn: (params) => run(async () => {
114
+ await client.signIn(params);
115
+ }),
116
+ signInWithOAuth: async (provider, options) => {
117
+ lastError = null;
118
+ try {
119
+ return await client.signInWithOAuth(provider, options);
120
+ } catch (error) {
121
+ captureError(error);
122
+ throw error instanceof Error ? error : new Error(String(error));
123
+ }
124
+ },
125
+ completeOAuthSignIn: (provider, params) => run(async () => {
126
+ await client.completeOAuthSignIn(provider, params);
127
+ }),
128
+ getOAuthAuthorizationUrl: async (provider, options) => {
129
+ lastError = null;
130
+ try {
131
+ return await client.getOAuthAuthorizationUrl(provider, options);
132
+ } catch (error) {
133
+ captureError(error);
134
+ throw error instanceof Error ? error : new Error(String(error));
135
+ }
136
+ },
137
+ linkOAuth: (provider, params) => runWithResult(async () => client.linkOAuth(provider, params)),
138
+ listLinkedAccounts: () => runWithResult(async () => client.listLinkedAccounts()).then((value) => value ?? []),
139
+ unlinkOAuth: (provider) => run(async () => {
140
+ await client.unlinkOAuth(provider);
141
+ }),
142
+ signOut: () => run(async () => {
143
+ await client.signOut();
144
+ }),
145
+ destroy() {
146
+ unsubscribeAuth();
147
+ listeners.clear();
148
+ }
149
+ };
150
+ }
151
+
38
152
  // src/react/auth-context.ts
39
153
  var import_react = require("react");
40
154
  var AuthContext = (0, import_react.createContext)(null);
41
155
 
42
156
  // src/react/AuthProvider.tsx
43
157
  function AuthProvider({ client, children, fallback }) {
44
- const [state, setState] = (0, import_react2.useState)(client.state);
45
- const [isLoading, setIsLoading] = (0, import_react2.useState)(true);
46
- const [initError, setInitError] = (0, import_react2.useState)(null);
47
- (0, import_react2.useEffect)(() => {
48
- let cancelled = false;
49
- const unsubscribe = client.onAuthChange((newState) => {
50
- if (!cancelled) {
51
- setState(newState);
52
- }
53
- });
54
- client.initialize().then(() => {
55
- if (!cancelled) {
56
- setState(client.state);
57
- setIsLoading(false);
58
- }
59
- }).catch((error) => {
60
- if (!cancelled) {
61
- const err = error instanceof Error ? error : new Error(String(error));
62
- console.error("[Kora Auth] Initialization failed:", err);
63
- setInitError(err);
64
- setIsLoading(false);
65
- }
66
- });
67
- return () => {
68
- cancelled = true;
69
- unsubscribe();
70
- };
71
- }, [client]);
72
- if (initError) {
158
+ const session = (0, import_react2.useMemo)(() => createAuthSession(client), [client]);
159
+ (0, import_react2.useEffect)(() => () => session.destroy(), [session]);
160
+ const snapshot = (0, import_react2.useSyncExternalStore)(
161
+ (onStoreChange) => session.subscribe(onStoreChange),
162
+ () => session.getSnapshot(),
163
+ () => session.getSnapshot()
164
+ );
165
+ if (snapshot.initError) {
73
166
  return (0, import_react2.createElement)(
74
167
  "div",
75
168
  {
@@ -77,16 +170,17 @@ function AuthProvider({ client, children, fallback }) {
77
170
  role: "alert"
78
171
  },
79
172
  (0, import_react2.createElement)("strong", null, "Kora Auth initialization error: "),
80
- initError.message
173
+ snapshot.initError.message
81
174
  );
82
175
  }
83
- if (isLoading && fallback !== void 0) {
176
+ if (snapshot.isLoading && fallback !== void 0) {
84
177
  return fallback;
85
178
  }
86
179
  const contextValue = {
87
180
  client,
88
- state,
89
- isLoading
181
+ session,
182
+ state: snapshot.state,
183
+ isLoading: snapshot.isLoading
90
184
  };
91
185
  return (0, import_react2.createElement)(AuthContext.Provider, { value: contextValue }, children);
92
186
  }
@@ -102,102 +196,130 @@ function useAuthContext() {
102
196
  }
103
197
  return ctx;
104
198
  }
199
+ function useAuthSessionSnapshot() {
200
+ const { session } = useAuthContext();
201
+ return (0, import_react3.useSyncExternalStore)(
202
+ (onStoreChange) => session.subscribe(onStoreChange),
203
+ () => session.getSnapshot(),
204
+ () => session.getSnapshot()
205
+ );
206
+ }
105
207
  function useAuth() {
106
- const { client, state, isLoading } = useAuthContext();
107
- const [error, setError] = (0, import_react3.useState)(null);
108
- const userSnapshotRef = (0, import_react3.useRef)(client.currentUser);
109
- const stateSerializedRef = (0, import_react3.useRef)(JSON.stringify(client.currentUser));
110
- const subscribe = (0, import_react3.useCallback)(
111
- (onStoreChange) => {
112
- return client.onAuthChange(() => {
113
- const newUser = client.currentUser;
114
- const newSerialized = JSON.stringify(newUser);
115
- if (newSerialized !== stateSerializedRef.current) {
116
- userSnapshotRef.current = newUser;
117
- stateSerializedRef.current = newSerialized;
118
- onStoreChange();
119
- }
120
- });
208
+ const { session } = useAuthContext();
209
+ const snapshot = useAuthSessionSnapshot();
210
+ return {
211
+ user: snapshot.user,
212
+ isAuthenticated: snapshot.isAuthenticated,
213
+ isLoading: snapshot.isLoading,
214
+ error: snapshot.error,
215
+ initError: snapshot.initError,
216
+ signUp: (params) => session.signUp(params),
217
+ signIn: (params) => session.signIn(params),
218
+ signInWithOAuth: (provider, options) => session.signInWithOAuth(provider, options),
219
+ completeOAuthSignIn: (provider, params) => session.completeOAuthSignIn(provider, params),
220
+ getOAuthAuthorizationUrl: (provider, options) => session.getOAuthAuthorizationUrl(provider, options),
221
+ linkOAuth: (provider, params) => session.linkOAuth(provider, params),
222
+ listLinkedAccounts: () => session.listLinkedAccounts(),
223
+ unlinkOAuth: (provider) => session.unlinkOAuth(provider),
224
+ signOut: () => session.signOut()
225
+ };
226
+ }
227
+ function useCurrentUser() {
228
+ return useAuthSessionSnapshot().user;
229
+ }
230
+ function useAuthStatus() {
231
+ const snapshot = useAuthSessionSnapshot();
232
+ return {
233
+ state: snapshot.state,
234
+ isAuthenticated: snapshot.isAuthenticated,
235
+ isLoading: snapshot.isLoading
236
+ };
237
+ }
238
+
239
+ // src/react/OrgProvider.tsx
240
+ var import_react5 = require("react");
241
+
242
+ // src/bindings/create-org-session.ts
243
+ var ROLE_LEVELS = {
244
+ viewer: 10,
245
+ billing: 15,
246
+ member: 20,
247
+ admin: 30,
248
+ owner: 40
249
+ };
250
+ function checkOrgPermission(currentRole, requiredRole) {
251
+ if (!currentRole) return false;
252
+ const currentLevel = ROLE_LEVELS[currentRole] ?? 0;
253
+ const requiredLevel = ROLE_LEVELS[requiredRole] ?? 0;
254
+ return currentLevel >= requiredLevel;
255
+ }
256
+ function createOrgSession(client) {
257
+ let snapshot = buildSnapshot(client);
258
+ const listeners = /* @__PURE__ */ new Set();
259
+ const notify = () => {
260
+ snapshot = buildSnapshot(client);
261
+ for (const listener of listeners) {
262
+ listener();
263
+ }
264
+ };
265
+ const unsubscribeOrgChange = client.onOrgChange(notify);
266
+ return {
267
+ client,
268
+ getSnapshot: () => snapshot,
269
+ subscribe(listener) {
270
+ listeners.add(listener);
271
+ return () => {
272
+ listeners.delete(listener);
273
+ };
121
274
  },
122
- [client]
123
- );
124
- const getSnapshot = (0, import_react3.useCallback)(() => {
125
- return userSnapshotRef.current;
126
- }, []);
127
- const user = (0, import_react3.useSyncExternalStore)(subscribe, getSnapshot);
128
- const signUp = (0, import_react3.useCallback)(
129
- async (params) => {
130
- setError(null);
275
+ checkPermission(requiredRole) {
276
+ return checkOrgPermission(snapshot.role, requiredRole);
277
+ },
278
+ destroy() {
279
+ unsubscribeOrgChange();
280
+ listeners.clear();
281
+ }
282
+ };
283
+ }
284
+ function buildSnapshot(client) {
285
+ return {
286
+ orgId: client.activeOrgId,
287
+ org: client.activeOrg,
288
+ role: client.activeRole
289
+ };
290
+ }
291
+ function createOrgMembersActions(client, orgId, onError) {
292
+ return {
293
+ async refresh() {
294
+ await client.listMembers(orgId);
295
+ },
296
+ async invite(email, role) {
131
297
  try {
132
- await client.signUp(params);
133
- } catch (err) {
134
- const message = err instanceof Error ? err.message : String(err);
135
- setError(message);
298
+ return await client.inviteMember(orgId, { email, role });
299
+ } catch (error) {
300
+ const message = error instanceof Error ? error.message : String(error);
301
+ onError(message);
302
+ throw error;
136
303
  }
137
304
  },
138
- [client]
139
- );
140
- const signIn = (0, import_react3.useCallback)(
141
- async (params) => {
142
- setError(null);
305
+ async removeMember(userId) {
143
306
  try {
144
- await client.signIn(params);
145
- } catch (err) {
146
- const message = err instanceof Error ? err.message : String(err);
147
- setError(message);
307
+ await client.removeMember(orgId, userId);
308
+ } catch (error) {
309
+ onError(error instanceof Error ? error.message : String(error));
148
310
  }
149
311
  },
150
- [client]
151
- );
152
- const signOut = (0, import_react3.useCallback)(async () => {
153
- setError(null);
154
- try {
155
- await client.signOut();
156
- } catch (err) {
157
- const message = err instanceof Error ? err.message : String(err);
158
- setError(message);
312
+ async updateRole(userId, role) {
313
+ try {
314
+ await client.updateMemberRole(orgId, userId, role);
315
+ } catch (error) {
316
+ onError(error instanceof Error ? error.message : String(error));
317
+ }
159
318
  }
160
- }, [client]);
161
- return {
162
- user,
163
- isAuthenticated: state === "authenticated",
164
- isLoading,
165
- signUp,
166
- signIn,
167
- signOut,
168
- error
169
319
  };
170
320
  }
171
- function useCurrentUser() {
172
- const { client } = useAuthContext();
173
- const userSnapshotRef = (0, import_react3.useRef)(client.currentUser);
174
- const stateSerializedRef = (0, import_react3.useRef)(JSON.stringify(client.currentUser));
175
- const subscribe = (0, import_react3.useCallback)(
176
- (onStoreChange) => {
177
- return client.onAuthChange(() => {
178
- const newUser = client.currentUser;
179
- const newSerialized = JSON.stringify(newUser);
180
- if (newSerialized !== stateSerializedRef.current) {
181
- userSnapshotRef.current = newUser;
182
- stateSerializedRef.current = newSerialized;
183
- onStoreChange();
184
- }
185
- });
186
- },
187
- [client]
188
- );
189
- const getSnapshot = (0, import_react3.useCallback)(() => {
190
- return userSnapshotRef.current;
191
- }, []);
192
- return (0, import_react3.useSyncExternalStore)(subscribe, getSnapshot);
193
- }
194
- function useAuthStatus() {
195
- const { state, isLoading } = useAuthContext();
196
- return {
197
- state,
198
- isAuthenticated: state === "authenticated",
199
- isLoading
200
- };
321
+ async function loadOrgMembers(client, orgId) {
322
+ return client.listMembers(orgId);
201
323
  }
202
324
 
203
325
  // src/react/org-hooks.ts
@@ -212,29 +334,24 @@ function useOrgContext() {
212
334
  }
213
335
  return ctx;
214
336
  }
215
- function useOrg() {
216
- const { client } = useOrgContext();
217
- const [error, setError] = (0, import_react4.useState)(null);
218
- const orgSnapshotRef = (0, import_react4.useRef)({
219
- orgId: client.activeOrgId,
220
- org: client.activeOrg,
221
- role: client.activeRole
222
- });
337
+ function useOrgSnapshot(session) {
338
+ const snapshotRef = (0, import_react4.useRef)(session.getSnapshot());
223
339
  const subscribe = (0, import_react4.useCallback)(
224
340
  (onStoreChange) => {
225
- return client.onOrgChange(() => {
226
- orgSnapshotRef.current = {
227
- orgId: client.activeOrgId,
228
- org: client.activeOrg,
229
- role: client.activeRole
230
- };
341
+ return session.subscribe(() => {
342
+ snapshotRef.current = session.getSnapshot();
231
343
  onStoreChange();
232
344
  });
233
345
  },
234
- [client]
346
+ [session]
235
347
  );
236
- const getSnapshot = (0, import_react4.useCallback)(() => orgSnapshotRef.current, []);
237
- const { orgId, org, role } = (0, import_react4.useSyncExternalStore)(subscribe, getSnapshot);
348
+ const getSnapshot = (0, import_react4.useCallback)(() => snapshotRef.current, []);
349
+ return (0, import_react4.useSyncExternalStore)(subscribe, getSnapshot);
350
+ }
351
+ function useOrg() {
352
+ const { client, session } = useOrgContext();
353
+ const { orgId, org, role } = useOrgSnapshot(session);
354
+ const [error, setError] = (0, import_react4.useState)(null);
238
355
  const switchOrg = (0, import_react4.useCallback)(
239
356
  async (newOrgId) => {
240
357
  setError(null);
@@ -290,8 +407,7 @@ function useOrgMembers(orgId) {
290
407
  setIsLoading(true);
291
408
  setError(null);
292
409
  try {
293
- const result = await client.listMembers(orgId);
294
- setMembers(result);
410
+ setMembers(await loadOrgMembers(client, orgId));
295
411
  } catch (err) {
296
412
  setError(err instanceof Error ? err.message : String(err));
297
413
  } finally {
@@ -299,84 +415,82 @@ function useOrgMembers(orgId) {
299
415
  }
300
416
  }, [client, orgId]);
301
417
  (0, import_react4.useEffect)(() => {
302
- refresh();
418
+ void refresh();
303
419
  }, [refresh]);
420
+ const actions = createOrgMembersActions(client, orgId, setError);
304
421
  const invite = (0, import_react4.useCallback)(
305
422
  async (email, role) => {
306
- setError(null);
307
- try {
308
- const result = await client.inviteMember(orgId, { email, role });
309
- return result;
310
- } catch (err) {
311
- const msg = err instanceof Error ? err.message : String(err);
312
- setError(msg);
313
- throw err;
314
- }
423
+ const result = await actions.invite(email, role);
424
+ await refresh();
425
+ return result;
315
426
  },
316
- [client, orgId]
427
+ [actions, refresh]
317
428
  );
318
429
  const removeMember = (0, import_react4.useCallback)(
319
430
  async (userId) => {
320
- setError(null);
321
- try {
322
- await client.removeMember(orgId, userId);
323
- await refresh();
324
- } catch (err) {
325
- setError(err instanceof Error ? err.message : String(err));
326
- }
431
+ await actions.removeMember(userId);
432
+ await refresh();
327
433
  },
328
- [client, orgId, refresh]
434
+ [actions, refresh]
329
435
  );
330
436
  const updateRole = (0, import_react4.useCallback)(
331
437
  async (userId, role) => {
332
- setError(null);
333
- try {
334
- await client.updateMemberRole(orgId, userId, role);
335
- await refresh();
336
- } catch (err) {
337
- setError(err instanceof Error ? err.message : String(err));
338
- }
438
+ await actions.updateRole(userId, role);
439
+ await refresh();
339
440
  },
340
- [client, orgId, refresh]
441
+ [actions, refresh]
341
442
  );
342
443
  return { members, isLoading, refresh, invite, removeMember, updateRole, error };
343
444
  }
344
445
  function usePermission(requiredRole) {
345
- const { client } = useOrgContext();
346
- const snapshotRef = (0, import_react4.useRef)(checkPermission(client.activeRole, requiredRole));
446
+ const { session } = useOrgContext();
447
+ const snapshotRef = (0, import_react4.useRef)(session.checkPermission(requiredRole));
347
448
  const subscribe = (0, import_react4.useCallback)(
348
449
  (onStoreChange) => {
349
- return client.onOrgChange(() => {
350
- const newValue = checkPermission(client.activeRole, requiredRole);
351
- if (newValue !== snapshotRef.current) {
352
- snapshotRef.current = newValue;
450
+ return session.subscribe(() => {
451
+ const next = session.checkPermission(requiredRole);
452
+ if (next !== snapshotRef.current) {
453
+ snapshotRef.current = next;
353
454
  onStoreChange();
354
455
  }
355
456
  });
356
457
  },
357
- [client, requiredRole]
458
+ [session, requiredRole]
358
459
  );
359
460
  const getSnapshot = (0, import_react4.useCallback)(() => snapshotRef.current, []);
360
461
  return (0, import_react4.useSyncExternalStore)(subscribe, getSnapshot);
361
462
  }
362
- var ROLE_LEVELS = {
363
- viewer: 10,
364
- billing: 15,
365
- member: 20,
366
- admin: 30,
367
- owner: 40
368
- };
369
- function checkPermission(currentRole, requiredRole) {
370
- if (!currentRole) return false;
371
- const currentLevel = ROLE_LEVELS[currentRole] ?? 0;
372
- const requiredLevel = ROLE_LEVELS[requiredRole] ?? 0;
373
- return currentLevel >= requiredLevel;
463
+
464
+ // src/react/OrgProvider.tsx
465
+ var import_jsx_runtime = require("react/jsx-runtime");
466
+ function OrgProvider({ client, children }) {
467
+ const sessionRef = (0, import_react5.useRef)(null);
468
+ if (sessionRef.current === null || sessionRef.current.client !== client) {
469
+ sessionRef.current?.destroy();
470
+ sessionRef.current = createOrgSession(client);
471
+ }
472
+ (0, import_react5.useEffect)(() => {
473
+ return () => {
474
+ sessionRef.current?.destroy();
475
+ sessionRef.current = null;
476
+ };
477
+ }, []);
478
+ const value = (0, import_react5.useMemo)(
479
+ () => ({
480
+ client,
481
+ session: sessionRef.current
482
+ }),
483
+ [client]
484
+ );
485
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(OrgContext.Provider, { value, children });
374
486
  }
375
487
  // Annotate the CommonJS export names for ESM import in node:
376
488
  0 && (module.exports = {
377
489
  AuthContext,
378
490
  AuthProvider,
379
491
  OrgContext,
492
+ OrgProvider,
493
+ checkOrgPermission,
380
494
  useAuth,
381
495
  useAuthStatus,
382
496
  useCurrentUser,