@korajs/auth 0.5.0 → 1.0.0-beta.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,181 +196,130 @@ function useAuthContext() {
102
196
  }
103
197
  return ctx;
104
198
  }
105
- 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
- });
121
- },
122
- [client]
199
+ function useAuthSessionSnapshot() {
200
+ const { session } = useAuthContext();
201
+ return (0, import_react3.useSyncExternalStore)(
202
+ (onStoreChange) => session.subscribe(onStoreChange),
203
+ () => session.getSnapshot(),
204
+ () => session.getSnapshot()
123
205
  );
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);
131
- try {
132
- await client.signUp(params);
133
- } catch (err) {
134
- const message = err instanceof Error ? err.message : String(err);
135
- setError(message);
136
- }
137
- },
138
- [client]
139
- );
140
- const signIn = (0, import_react3.useCallback)(
141
- async (params) => {
142
- setError(null);
143
- try {
144
- await client.signIn(params);
145
- } catch (err) {
146
- const message = err instanceof Error ? err.message : String(err);
147
- setError(message);
148
- }
206
+ }
207
+ function useAuth() {
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
+ };
149
274
  },
150
- [client]
151
- );
152
- const signInWithOAuth = (0, import_react3.useCallback)(
153
- async (provider, options) => {
154
- setError(null);
155
- try {
156
- return await client.signInWithOAuth(provider, options);
157
- } catch (err) {
158
- const message = err instanceof Error ? err.message : String(err);
159
- setError(message);
160
- throw err;
161
- }
275
+ checkPermission(requiredRole) {
276
+ return checkOrgPermission(snapshot.role, requiredRole);
162
277
  },
163
- [client]
164
- );
165
- const completeOAuthSignIn = (0, import_react3.useCallback)(
166
- async (provider, params) => {
167
- setError(null);
168
- try {
169
- await client.completeOAuthSignIn(provider, params);
170
- } catch (err) {
171
- const message = err instanceof Error ? err.message : String(err);
172
- setError(message);
173
- }
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);
174
295
  },
175
- [client]
176
- );
177
- const getOAuthAuthorizationUrl = (0, import_react3.useCallback)(
178
- async (provider, options) => {
179
- setError(null);
296
+ async invite(email, role) {
180
297
  try {
181
- return await client.getOAuthAuthorizationUrl(provider, options);
182
- } catch (err) {
183
- const message = err instanceof Error ? err.message : String(err);
184
- setError(message);
185
- throw err;
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;
186
303
  }
187
304
  },
188
- [client]
189
- );
190
- const linkOAuth = (0, import_react3.useCallback)(
191
- async (provider, params) => {
192
- setError(null);
305
+ async removeMember(userId) {
193
306
  try {
194
- return await client.linkOAuth(provider, params);
195
- } catch (err) {
196
- const message = err instanceof Error ? err.message : String(err);
197
- setError(message);
198
- return null;
307
+ await client.removeMember(orgId, userId);
308
+ } catch (error) {
309
+ onError(error instanceof Error ? error.message : String(error));
199
310
  }
200
311
  },
201
- [client]
202
- );
203
- const listLinkedAccounts = (0, import_react3.useCallback)(async () => {
204
- setError(null);
205
- try {
206
- return await client.listLinkedAccounts();
207
- } catch (err) {
208
- const message = err instanceof Error ? err.message : String(err);
209
- setError(message);
210
- return [];
211
- }
212
- }, [client]);
213
- const unlinkOAuth = (0, import_react3.useCallback)(
214
- async (provider) => {
215
- setError(null);
312
+ async updateRole(userId, role) {
216
313
  try {
217
- await client.unlinkOAuth(provider);
218
- } catch (err) {
219
- const message = err instanceof Error ? err.message : String(err);
220
- setError(message);
314
+ await client.updateMemberRole(orgId, userId, role);
315
+ } catch (error) {
316
+ onError(error instanceof Error ? error.message : String(error));
221
317
  }
222
- },
223
- [client]
224
- );
225
- const signOut = (0, import_react3.useCallback)(async () => {
226
- setError(null);
227
- try {
228
- await client.signOut();
229
- } catch (err) {
230
- const message = err instanceof Error ? err.message : String(err);
231
- setError(message);
232
318
  }
233
- }, [client]);
234
- return {
235
- user,
236
- isAuthenticated: state === "authenticated",
237
- isLoading,
238
- signUp,
239
- signIn,
240
- signInWithOAuth,
241
- completeOAuthSignIn,
242
- getOAuthAuthorizationUrl,
243
- linkOAuth,
244
- listLinkedAccounts,
245
- unlinkOAuth,
246
- signOut,
247
- error
248
319
  };
249
320
  }
250
- function useCurrentUser() {
251
- const { client } = useAuthContext();
252
- const userSnapshotRef = (0, import_react3.useRef)(client.currentUser);
253
- const stateSerializedRef = (0, import_react3.useRef)(JSON.stringify(client.currentUser));
254
- const subscribe = (0, import_react3.useCallback)(
255
- (onStoreChange) => {
256
- return client.onAuthChange(() => {
257
- const newUser = client.currentUser;
258
- const newSerialized = JSON.stringify(newUser);
259
- if (newSerialized !== stateSerializedRef.current) {
260
- userSnapshotRef.current = newUser;
261
- stateSerializedRef.current = newSerialized;
262
- onStoreChange();
263
- }
264
- });
265
- },
266
- [client]
267
- );
268
- const getSnapshot = (0, import_react3.useCallback)(() => {
269
- return userSnapshotRef.current;
270
- }, []);
271
- return (0, import_react3.useSyncExternalStore)(subscribe, getSnapshot);
272
- }
273
- function useAuthStatus() {
274
- const { state, isLoading } = useAuthContext();
275
- return {
276
- state,
277
- isAuthenticated: state === "authenticated",
278
- isLoading
279
- };
321
+ async function loadOrgMembers(client, orgId) {
322
+ return client.listMembers(orgId);
280
323
  }
281
324
 
282
325
  // src/react/org-hooks.ts
@@ -291,29 +334,24 @@ function useOrgContext() {
291
334
  }
292
335
  return ctx;
293
336
  }
294
- function useOrg() {
295
- const { client } = useOrgContext();
296
- const [error, setError] = (0, import_react4.useState)(null);
297
- const orgSnapshotRef = (0, import_react4.useRef)({
298
- orgId: client.activeOrgId,
299
- org: client.activeOrg,
300
- role: client.activeRole
301
- });
337
+ function useOrgSnapshot(session) {
338
+ const snapshotRef = (0, import_react4.useRef)(session.getSnapshot());
302
339
  const subscribe = (0, import_react4.useCallback)(
303
340
  (onStoreChange) => {
304
- return client.onOrgChange(() => {
305
- orgSnapshotRef.current = {
306
- orgId: client.activeOrgId,
307
- org: client.activeOrg,
308
- role: client.activeRole
309
- };
341
+ return session.subscribe(() => {
342
+ snapshotRef.current = session.getSnapshot();
310
343
  onStoreChange();
311
344
  });
312
345
  },
313
- [client]
346
+ [session]
314
347
  );
315
- const getSnapshot = (0, import_react4.useCallback)(() => orgSnapshotRef.current, []);
316
- 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);
317
355
  const switchOrg = (0, import_react4.useCallback)(
318
356
  async (newOrgId) => {
319
357
  setError(null);
@@ -369,8 +407,7 @@ function useOrgMembers(orgId) {
369
407
  setIsLoading(true);
370
408
  setError(null);
371
409
  try {
372
- const result = await client.listMembers(orgId);
373
- setMembers(result);
410
+ setMembers(await loadOrgMembers(client, orgId));
374
411
  } catch (err) {
375
412
  setError(err instanceof Error ? err.message : String(err));
376
413
  } finally {
@@ -378,84 +415,83 @@ function useOrgMembers(orgId) {
378
415
  }
379
416
  }, [client, orgId]);
380
417
  (0, import_react4.useEffect)(() => {
381
- refresh();
418
+ void refresh();
382
419
  }, [refresh]);
420
+ const actions = createOrgMembersActions(client, orgId, setError);
383
421
  const invite = (0, import_react4.useCallback)(
384
422
  async (email, role) => {
385
- setError(null);
386
- try {
387
- const result = await client.inviteMember(orgId, { email, role });
388
- return result;
389
- } catch (err) {
390
- const msg = err instanceof Error ? err.message : String(err);
391
- setError(msg);
392
- throw err;
393
- }
423
+ const result = await actions.invite(email, role);
424
+ await refresh();
425
+ return result;
394
426
  },
395
- [client, orgId]
427
+ [actions, refresh]
396
428
  );
397
429
  const removeMember = (0, import_react4.useCallback)(
398
430
  async (userId) => {
399
- setError(null);
400
- try {
401
- await client.removeMember(orgId, userId);
402
- await refresh();
403
- } catch (err) {
404
- setError(err instanceof Error ? err.message : String(err));
405
- }
431
+ await actions.removeMember(userId);
432
+ await refresh();
406
433
  },
407
- [client, orgId, refresh]
434
+ [actions, refresh]
408
435
  );
409
436
  const updateRole = (0, import_react4.useCallback)(
410
437
  async (userId, role) => {
411
- setError(null);
412
- try {
413
- await client.updateMemberRole(orgId, userId, role);
414
- await refresh();
415
- } catch (err) {
416
- setError(err instanceof Error ? err.message : String(err));
417
- }
438
+ await actions.updateRole(userId, role);
439
+ await refresh();
418
440
  },
419
- [client, orgId, refresh]
441
+ [actions, refresh]
420
442
  );
421
443
  return { members, isLoading, refresh, invite, removeMember, updateRole, error };
422
444
  }
423
445
  function usePermission(requiredRole) {
424
- const { client } = useOrgContext();
425
- const snapshotRef = (0, import_react4.useRef)(checkPermission(client.activeRole, requiredRole));
446
+ const { session } = useOrgContext();
447
+ const snapshotRef = (0, import_react4.useRef)(session.checkPermission(requiredRole));
426
448
  const subscribe = (0, import_react4.useCallback)(
427
449
  (onStoreChange) => {
428
- return client.onOrgChange(() => {
429
- const newValue = checkPermission(client.activeRole, requiredRole);
430
- if (newValue !== snapshotRef.current) {
431
- snapshotRef.current = newValue;
450
+ return session.subscribe(() => {
451
+ const next = session.checkPermission(requiredRole);
452
+ if (next !== snapshotRef.current) {
453
+ snapshotRef.current = next;
432
454
  onStoreChange();
433
455
  }
434
456
  });
435
457
  },
436
- [client, requiredRole]
458
+ [session, requiredRole]
437
459
  );
438
460
  const getSnapshot = (0, import_react4.useCallback)(() => snapshotRef.current, []);
439
461
  return (0, import_react4.useSyncExternalStore)(subscribe, getSnapshot);
440
462
  }
441
- var ROLE_LEVELS = {
442
- viewer: 10,
443
- billing: 15,
444
- member: 20,
445
- admin: 30,
446
- owner: 40
447
- };
448
- function checkPermission(currentRole, requiredRole) {
449
- if (!currentRole) return false;
450
- const currentLevel = ROLE_LEVELS[currentRole] ?? 0;
451
- const requiredLevel = ROLE_LEVELS[requiredRole] ?? 0;
452
- 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 session = sessionRef.current;
479
+ const value = (0, import_react5.useMemo)(
480
+ () => ({
481
+ client,
482
+ session
483
+ }),
484
+ [client, session]
485
+ );
486
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(OrgContext.Provider, { value, children });
453
487
  }
454
488
  // Annotate the CommonJS export names for ESM import in node:
455
489
  0 && (module.exports = {
456
490
  AuthContext,
457
491
  AuthProvider,
458
492
  OrgContext,
493
+ OrgProvider,
494
+ checkOrgPermission,
459
495
  useAuth,
460
496
  useAuthStatus,
461
497
  useCurrentUser,