@elvix.is/sdk 0.4.0 → 0.5.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/dist/react.d.ts CHANGED
@@ -67,6 +67,12 @@ type ElvixSignInResultOk = {
67
67
  redirect?: string;
68
68
  /** Sign-in factor that succeeded. */
69
69
  method: ElvixSignInMethod;
70
+ /**
71
+ * Cross-origin only: the session token. Pass it to your backend and verify
72
+ * it with `verifyElvixToken` from `@elvix.is/sdk/server`. Undefined for
73
+ * same-origin sign-in (the session rides a cookie instead).
74
+ */
75
+ token?: string;
70
76
  };
71
77
  type ElvixSignInResultErr = {
72
78
  ok: false;
@@ -120,6 +126,48 @@ declare function ElvixSignIn({ onResult, redirectAfterSignIn, className, }: {
120
126
  className?: string;
121
127
  }): react.JSX.Element;
122
128
 
129
+ /**
130
+ * Session token store for cross-origin SDK use.
131
+ *
132
+ * When the SDK runs on a customer app's own origin it can't use elvix's
133
+ * session cookie (third-party cookies are blocked), so sign-in returns a
134
+ * token that every subsequent call carries as `Authorization: Bearer`.
135
+ * `<ElvixSignIn>` stores it here; `appPost/appPatch/appDelete` and the live
136
+ * hooks attach it. Same-origin hosts never store a token, so these are no-ops
137
+ * and the cookie path is used unchanged.
138
+ */
139
+ /** Current session token (memory first, then localStorage), or null. */
140
+ declare function getElvixToken(): string | null;
141
+ /** Store (or clear with null) the session token. Persists to localStorage. */
142
+ declare function setElvixToken(token: string | null): void;
143
+
144
+ type UseUserListResult = {
145
+ slugs: string[];
146
+ loading: boolean;
147
+ error: string | null;
148
+ refresh: () => Promise<void>;
149
+ };
150
+ type Opts = {
151
+ applicationId: string;
152
+ userId: string;
153
+ /** elvix origin. Defaults to "" (same-origin). */
154
+ baseUrl?: string;
155
+ /** Poll interval in ms. Default 7000. */
156
+ pollMs?: number;
157
+ };
158
+ declare const useUserRoles: (opts: Opts) => UseUserListResult;
159
+ declare const useUserScopes: (opts: Opts) => UseUserListResult;
160
+ declare const useUserMemberships: (opts: Opts) => UseUserListResult;
161
+
162
+ declare function ElvixLifecycleWatcher({ baseUrl, pollMs, onSignedOut, }: {
163
+ /** elvix origin. Defaults to "" (same-origin). */
164
+ baseUrl?: string;
165
+ /** Poll interval in ms. Default 7000. */
166
+ pollMs?: number;
167
+ /** Called once with the reason when the session ends. Defaults to a reload. */
168
+ onSignedOut?: (reason: string) => void;
169
+ }): null;
170
+
123
171
  /**
124
172
  * `<ElvixUsername>` — claim or change the end-user's username for the
125
173
  * current Application. PATCH /api/account/apps/<appId>/username.
@@ -232,4 +280,4 @@ declare function ElvixLegalEntities({ onResult, }: {
232
280
  onResult?: (r: ElvixActionResult) => void;
233
281
  }): react.JSX.Element;
234
282
 
235
- export { ElvixActionResult, ElvixAddressBook, ElvixAvatar, ElvixBanner, type ElvixBootstrapEnvelope, type ElvixBrand, ElvixCard, ElvixDeactivate, ElvixExport, ElvixIdentityForm, ElvixLanguages, ElvixLeave, ElvixLegalEntities, ElvixProvider, ElvixRegion, ElvixSessions, ElvixSignIn, type ElvixSignInMethod, type ElvixSignInResult, type ElvixSignInResultErr, type ElvixSignInResultOk, type ElvixTheme, ElvixUsername, useElvixApp, useElvixContext };
283
+ export { ElvixActionResult, ElvixAddressBook, ElvixAvatar, ElvixBanner, type ElvixBootstrapEnvelope, type ElvixBrand, ElvixCard, ElvixDeactivate, ElvixExport, ElvixIdentityForm, ElvixLanguages, ElvixLeave, ElvixLegalEntities, ElvixLifecycleWatcher, ElvixProvider, ElvixRegion, ElvixSessions, ElvixSignIn, type ElvixSignInMethod, type ElvixSignInResult, type ElvixSignInResultErr, type ElvixSignInResultOk, type ElvixTheme, ElvixUsername, type UseUserListResult, getElvixToken, setElvixToken, useElvixApp, useElvixContext, useUserMemberships, useUserRoles, useUserScopes };
package/dist/react.js CHANGED
@@ -166,6 +166,44 @@ function withAlpha(hex, a) {
166
166
 
167
167
  // src/react/elvix-sign-in.tsx
168
168
  import { useState as useState2 } from "react";
169
+
170
+ // src/react/session.ts
171
+ var STORAGE_KEY = "elvix.session.token";
172
+ var memToken = null;
173
+ function getElvixToken() {
174
+ if (memToken) return memToken;
175
+ if (typeof window !== "undefined") {
176
+ try {
177
+ memToken = window.localStorage.getItem(STORAGE_KEY);
178
+ } catch {
179
+ }
180
+ }
181
+ return memToken;
182
+ }
183
+ function setElvixToken(token) {
184
+ memToken = token;
185
+ if (typeof window === "undefined") return;
186
+ try {
187
+ if (token) window.localStorage.setItem(STORAGE_KEY, token);
188
+ else window.localStorage.removeItem(STORAGE_KEY);
189
+ } catch {
190
+ }
191
+ }
192
+ function authInit() {
193
+ const token = getElvixToken();
194
+ return token ? { headers: { authorization: `Bearer ${token}` }, credentials: "omit" } : { headers: {}, credentials: "include" };
195
+ }
196
+ function isSameOrigin(baseUrl) {
197
+ if (!baseUrl) return true;
198
+ if (typeof window === "undefined") return true;
199
+ try {
200
+ return new URL(baseUrl, window.location.href).origin === window.location.origin;
201
+ } catch {
202
+ return true;
203
+ }
204
+ }
205
+
206
+ // src/react/elvix-sign-in.tsx
169
207
  function ElvixSignIn({
170
208
  onResult,
171
209
  redirectAfterSignIn,
@@ -199,7 +237,7 @@ function ElvixSignIn({
199
237
  const res = await fetch(`${ctx.baseUrl}/api/auth/otp/start`, {
200
238
  method: "POST",
201
239
  headers: { "content-type": "application/json" },
202
- credentials: "include",
240
+ credentials: isSameOrigin(ctx.baseUrl) ? "include" : "omit",
203
241
  body: JSON.stringify({
204
242
  email: email.trim(),
205
243
  intent: "app",
@@ -228,18 +266,20 @@ function ElvixSignIn({
228
266
  const res = await fetch(`${ctx.baseUrl}/api/auth/otp/verify`, {
229
267
  method: "POST",
230
268
  headers: { "content-type": "application/json" },
231
- credentials: "include",
269
+ credentials: isSameOrigin(ctx.baseUrl) ? "include" : "omit",
232
270
  body: JSON.stringify({ challengeId, code: code.trim() })
233
271
  });
234
272
  const body = await res.json();
235
273
  if (!res.ok || !body.success) {
236
274
  return fail(body.errorMessage ?? "otp_verify_failed");
237
275
  }
276
+ if (body.data?.token) setElvixToken(body.data.token);
238
277
  setStep("done");
239
278
  onResult?.({
240
279
  ok: true,
241
280
  method: "email_otp",
242
- redirect: body.data?.redirect ?? redirectAfterSignIn
281
+ redirect: body.data?.redirect ?? redirectAfterSignIn,
282
+ token: body.data?.token
243
283
  });
244
284
  } catch (e2) {
245
285
  fail("network", e2 instanceof Error ? e2.message : void 0);
@@ -289,16 +329,90 @@ function ElvixSignIn({
289
329
  ), /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: busy, className: "elvix-btn elvix-btn-primary" }, busy ? "Verifying\u2026" : verb)), error && /* @__PURE__ */ React.createElement("p", { role: "alert", className: "elvix-error" }, error));
290
330
  }
291
331
 
332
+ // src/react/hooks.ts
333
+ import { useCallback, useEffect as useEffect2, useState as useState3 } from "react";
334
+ var POLL_MS = 7e3;
335
+ function useUserList(kind, opts) {
336
+ const { applicationId, baseUrl = "", pollMs = POLL_MS } = opts;
337
+ const [slugs, setSlugs] = useState3([]);
338
+ const [loading, setLoading] = useState3(true);
339
+ const [error, setError] = useState3(null);
340
+ const refresh = useCallback(async () => {
341
+ setError(null);
342
+ try {
343
+ const res = await fetch(
344
+ `${baseUrl}/api/me/${kind}?applicationId=${encodeURIComponent(applicationId)}`,
345
+ authInit()
346
+ );
347
+ const json = await res.json().catch(() => ({}));
348
+ if (!res.ok || json.success === false) {
349
+ setError(json.errorMessage ?? `http_${res.status}`);
350
+ return;
351
+ }
352
+ setSlugs(json.data?.slugs ?? []);
353
+ } catch (e) {
354
+ setError(e instanceof Error ? e.message : "network");
355
+ } finally {
356
+ setLoading(false);
357
+ }
358
+ }, [applicationId, baseUrl, kind]);
359
+ useEffect2(() => {
360
+ refresh();
361
+ const id = setInterval(refresh, pollMs);
362
+ return () => clearInterval(id);
363
+ }, [refresh, pollMs]);
364
+ return { slugs, loading, error, refresh };
365
+ }
366
+ var useUserRoles = (opts) => useUserList("roles", opts);
367
+ var useUserScopes = (opts) => useUserList("scopes", opts);
368
+ var useUserMemberships = (opts) => useUserList("memberships", opts);
369
+
370
+ // src/react/lifecycle-watcher.tsx
371
+ import { useEffect as useEffect3 } from "react";
372
+ function ElvixLifecycleWatcher({
373
+ baseUrl = "",
374
+ pollMs = 7e3,
375
+ onSignedOut
376
+ }) {
377
+ useEffect3(() => {
378
+ let cancelled = false;
379
+ let fired = false;
380
+ const poll = async () => {
381
+ try {
382
+ const res = await fetch(`${baseUrl}/api/v1/session`, { method: "POST", ...authInit() });
383
+ const body = await res.json().catch(() => ({}));
384
+ if (cancelled || fired) return;
385
+ if (!body.ok) {
386
+ fired = true;
387
+ setElvixToken(null);
388
+ const reason = body.error ?? "signed_out";
389
+ if (onSignedOut) onSignedOut(reason);
390
+ else if (typeof window !== "undefined") window.location.reload();
391
+ }
392
+ } catch {
393
+ }
394
+ };
395
+ void poll();
396
+ const id = setInterval(poll, pollMs);
397
+ return () => {
398
+ cancelled = true;
399
+ clearInterval(id);
400
+ };
401
+ }, [baseUrl, pollMs, onSignedOut]);
402
+ return null;
403
+ }
404
+
292
405
  // src/react/elvix-username.tsx
293
- import { useState as useState3 } from "react";
406
+ import { useState as useState4 } from "react";
294
407
 
295
408
  // src/react/lib.ts
296
409
  async function appPost(opts, path, body) {
297
410
  try {
411
+ const auth = authInit();
298
412
  const res = await fetch(`${opts.baseUrl}/api/account/apps/${opts.applicationId}${path}`, {
299
413
  method: "POST",
300
- headers: { "content-type": "application/json" },
301
- credentials: "include",
414
+ headers: { "content-type": "application/json", ...auth.headers },
415
+ credentials: auth.credentials,
302
416
  body: JSON.stringify(body)
303
417
  });
304
418
  const json = await res.json();
@@ -312,10 +426,11 @@ async function appPost(opts, path, body) {
312
426
  }
313
427
  async function appPatch(opts, path, body) {
314
428
  try {
429
+ const auth = authInit();
315
430
  const res = await fetch(`${opts.baseUrl}/api/account/apps/${opts.applicationId}${path}`, {
316
431
  method: "PATCH",
317
- headers: { "content-type": "application/json" },
318
- credentials: "include",
432
+ headers: { "content-type": "application/json", ...auth.headers },
433
+ credentials: auth.credentials,
319
434
  body: JSON.stringify(body)
320
435
  });
321
436
  const json = await res.json();
@@ -329,9 +444,11 @@ async function appPatch(opts, path, body) {
329
444
  }
330
445
  async function appDelete(opts, path) {
331
446
  try {
447
+ const auth = authInit();
332
448
  const res = await fetch(`${opts.baseUrl}/api/account/apps/${opts.applicationId}${path}`, {
333
449
  method: "DELETE",
334
- credentials: "include"
450
+ headers: { ...auth.headers },
451
+ credentials: auth.credentials
335
452
  });
336
453
  const json = await res.json().catch(() => ({}));
337
454
  if (!res.ok || json.success !== void 0 && !json.success) {
@@ -348,10 +465,10 @@ function ElvixUsername({
348
465
  onResult
349
466
  }) {
350
467
  const ctx = useElvixContext();
351
- const [value, setValue] = useState3("");
352
- const [busy, setBusy] = useState3(false);
353
- const [error, setError] = useState3(null);
354
- const [done, setDone] = useState3(null);
468
+ const [value, setValue] = useState4("");
469
+ const [busy, setBusy] = useState4(false);
470
+ const [error, setError] = useState4(null);
471
+ const [done, setDone] = useState4(null);
355
472
  async function submit(e) {
356
473
  e.preventDefault();
357
474
  if (!ctx.app) return;
@@ -389,14 +506,14 @@ function ElvixUsername({
389
506
  }
390
507
 
391
508
  // src/react/elvix-avatar.tsx
392
- import { useState as useState4 } from "react";
509
+ import { useState as useState5 } from "react";
393
510
  function ElvixAvatar({
394
511
  onResult
395
512
  }) {
396
513
  const ctx = useElvixContext();
397
- const [busy, setBusy] = useState4(false);
398
- const [error, setError] = useState4(null);
399
- const [preview, setPreview] = useState4(null);
514
+ const [busy, setBusy] = useState5(false);
515
+ const [error, setError] = useState5(null);
516
+ const [preview, setPreview] = useState5(null);
400
517
  async function onFile(e) {
401
518
  const file = e.target.files?.[0];
402
519
  if (!file || !ctx.app) return;
@@ -430,14 +547,14 @@ function ElvixAvatar({
430
547
  }
431
548
 
432
549
  // src/react/elvix-banner.tsx
433
- import { useState as useState5 } from "react";
550
+ import { useState as useState6 } from "react";
434
551
  function ElvixBanner({
435
552
  onResult
436
553
  }) {
437
554
  const ctx = useElvixContext();
438
- const [busy, setBusy] = useState5(false);
439
- const [error, setError] = useState5(null);
440
- const [preview, setPreview] = useState5(null);
555
+ const [busy, setBusy] = useState6(false);
556
+ const [error, setError] = useState6(null);
557
+ const [preview, setPreview] = useState6(null);
441
558
  async function onFile(e) {
442
559
  const file = e.target.files?.[0];
443
560
  if (!file || !ctx.app) return;
@@ -471,18 +588,18 @@ function ElvixBanner({
471
588
  }
472
589
 
473
590
  // src/react/elvix-identity-form.tsx
474
- import { useState as useState6 } from "react";
591
+ import { useState as useState7 } from "react";
475
592
  function ElvixIdentityForm({
476
593
  initialName = "",
477
594
  initialBio = "",
478
595
  onResult
479
596
  }) {
480
597
  const ctx = useElvixContext();
481
- const [name, setName] = useState6(initialName);
482
- const [bio, setBio] = useState6(initialBio);
483
- const [busy, setBusy] = useState6(false);
484
- const [error, setError] = useState6(null);
485
- const [saved, setSaved] = useState6(false);
598
+ const [name, setName] = useState7(initialName);
599
+ const [bio, setBio] = useState7(initialBio);
600
+ const [busy, setBusy] = useState7(false);
601
+ const [error, setError] = useState7(null);
602
+ const [saved, setSaved] = useState7(false);
486
603
  async function submit(e) {
487
604
  e.preventDefault();
488
605
  if (!ctx.app) return;
@@ -502,18 +619,18 @@ function ElvixIdentityForm({
502
619
  }
503
620
 
504
621
  // src/react/elvix-region.tsx
505
- import { useState as useState7 } from "react";
622
+ import { useState as useState8 } from "react";
506
623
  function ElvixRegion({
507
624
  initialCountry = "",
508
625
  initialTimezone = "",
509
626
  onResult
510
627
  }) {
511
628
  const ctx = useElvixContext();
512
- const [country, setCountry] = useState7(initialCountry);
513
- const [timezone, setTimezone] = useState7(initialTimezone);
514
- const [busy, setBusy] = useState7(false);
515
- const [error, setError] = useState7(null);
516
- const [saved, setSaved] = useState7(false);
629
+ const [country, setCountry] = useState8(initialCountry);
630
+ const [timezone, setTimezone] = useState8(initialTimezone);
631
+ const [busy, setBusy] = useState8(false);
632
+ const [error, setError] = useState8(null);
633
+ const [saved, setSaved] = useState8(false);
517
634
  async function submit(e) {
518
635
  e.preventDefault();
519
636
  if (!ctx.app) return;
@@ -533,16 +650,16 @@ function ElvixRegion({
533
650
  }
534
651
 
535
652
  // src/react/elvix-languages.tsx
536
- import { useState as useState8 } from "react";
653
+ import { useState as useState9 } from "react";
537
654
  function ElvixLanguages({
538
655
  initial = [],
539
656
  onResult
540
657
  }) {
541
658
  const ctx = useElvixContext();
542
- const [raw, setRaw] = useState8(initial.join(", "));
543
- const [busy, setBusy] = useState8(false);
544
- const [error, setError] = useState8(null);
545
- const [saved, setSaved] = useState8(false);
659
+ const [raw, setRaw] = useState9(initial.join(", "));
660
+ const [busy, setBusy] = useState9(false);
661
+ const [error, setError] = useState9(null);
662
+ const [saved, setSaved] = useState9(false);
546
663
  async function submit(e) {
547
664
  e.preventDefault();
548
665
  if (!ctx.app) return;
@@ -563,18 +680,18 @@ function ElvixLanguages({
563
680
  }
564
681
 
565
682
  // src/react/elvix-sessions.tsx
566
- import { useEffect as useEffect2, useState as useState9 } from "react";
683
+ import { useEffect as useEffect4, useState as useState10 } from "react";
567
684
  function ElvixSessions({
568
685
  onResult
569
686
  }) {
570
687
  const ctx = useElvixContext();
571
- const [rows, setRows] = useState9(null);
572
- const [error, setError] = useState9(null);
573
- const [busy, setBusy] = useState9(false);
574
- useEffect2(() => {
688
+ const [rows, setRows] = useState10(null);
689
+ const [error, setError] = useState10(null);
690
+ const [busy, setBusy] = useState10(false);
691
+ useEffect4(() => {
575
692
  if (!ctx.app) return;
576
693
  fetch(`${ctx.baseUrl}/api/account/apps/${ctx.app.applicationId}/sessions`, {
577
- credentials: "include"
694
+ ...authInit()
578
695
  }).then((r) => r.json()).then((j) => {
579
696
  if (j.success && j.data) setRows(j.data.sessions);
580
697
  else setError("load_failed");
@@ -595,14 +712,14 @@ function ElvixSessions({
595
712
  }
596
713
 
597
714
  // src/react/elvix-export.tsx
598
- import { useState as useState10 } from "react";
715
+ import { useState as useState11 } from "react";
599
716
  function ElvixExport({
600
717
  onResult
601
718
  }) {
602
719
  const ctx = useElvixContext();
603
- const [busy, setBusy] = useState10(false);
604
- const [done, setDone] = useState10(false);
605
- const [error, setError] = useState10(null);
720
+ const [busy, setBusy] = useState11(false);
721
+ const [done, setDone] = useState11(false);
722
+ const [error, setError] = useState11(null);
606
723
  async function start() {
607
724
  if (!ctx.app) return;
608
725
  setBusy(true);
@@ -621,16 +738,16 @@ function ElvixExport({
621
738
  }
622
739
 
623
740
  // src/react/elvix-deactivate.tsx
624
- import { useState as useState11 } from "react";
741
+ import { useState as useState12 } from "react";
625
742
  function ElvixDeactivate({
626
743
  onResult
627
744
  }) {
628
745
  const ctx = useElvixContext();
629
- const [pane, setPane] = useState11("warn");
630
- const [challengeId, setChallengeId] = useState11(null);
631
- const [code, setCode] = useState11("");
632
- const [busy, setBusy] = useState11(false);
633
- const [error, setError] = useState11(null);
746
+ const [pane, setPane] = useState12("warn");
747
+ const [challengeId, setChallengeId] = useState12(null);
748
+ const [code, setCode] = useState12("");
749
+ const [busy, setBusy] = useState12(false);
750
+ const [error, setError] = useState12(null);
634
751
  async function startChallenge() {
635
752
  if (!ctx.app) return;
636
753
  setBusy(true);
@@ -687,16 +804,16 @@ function ElvixDeactivate({
687
804
  }
688
805
 
689
806
  // src/react/elvix-leave.tsx
690
- import { useState as useState12 } from "react";
807
+ import { useState as useState13 } from "react";
691
808
  function ElvixLeave({
692
809
  onResult
693
810
  }) {
694
811
  const ctx = useElvixContext();
695
- const [pane, setPane] = useState12("warn");
696
- const [challengeId, setChallengeId] = useState12(null);
697
- const [code, setCode] = useState12("");
698
- const [busy, setBusy] = useState12(false);
699
- const [error, setError] = useState12(null);
812
+ const [pane, setPane] = useState13("warn");
813
+ const [challengeId, setChallengeId] = useState13(null);
814
+ const [code, setCode] = useState13("");
815
+ const [busy, setBusy] = useState13(false);
816
+ const [error, setError] = useState13(null);
700
817
  async function startChallenge() {
701
818
  if (!ctx.app) return;
702
819
  setBusy(true);
@@ -753,16 +870,16 @@ function ElvixLeave({
753
870
  }
754
871
 
755
872
  // src/react/elvix-address-book.tsx
756
- import { useEffect as useEffect3, useState as useState13 } from "react";
873
+ import { useEffect as useEffect5, useState as useState14 } from "react";
757
874
  function ElvixAddressBook({
758
875
  onResult
759
876
  }) {
760
877
  const ctx = useElvixContext();
761
- const [rows, setRows] = useState13(null);
762
- const [error, setError] = useState13(null);
763
- const [busy, setBusy] = useState13(false);
764
- const [adding, setAdding] = useState13(false);
765
- const [form, setForm] = useState13({
878
+ const [rows, setRows] = useState14(null);
879
+ const [error, setError] = useState14(null);
880
+ const [busy, setBusy] = useState14(false);
881
+ const [adding, setAdding] = useState14(false);
882
+ const [form, setForm] = useState14({
766
883
  label: "Home",
767
884
  line1: "",
768
885
  postalCode: "",
@@ -772,13 +889,13 @@ function ElvixAddressBook({
772
889
  function reload() {
773
890
  if (!ctx.app) return;
774
891
  fetch(`${ctx.baseUrl}/api/account/apps/${ctx.app.applicationId}/addresses`, {
775
- credentials: "include"
892
+ ...authInit()
776
893
  }).then((r) => r.json()).then((j) => {
777
894
  if (j.success && j.data) setRows(j.data.addresses);
778
895
  else setError("load_failed");
779
896
  }).catch(() => setError("network"));
780
897
  }
781
- useEffect3(() => {
898
+ useEffect5(() => {
782
899
  reload();
783
900
  }, [ctx.app, ctx.baseUrl]);
784
901
  async function add(e) {
@@ -815,16 +932,16 @@ function ElvixAddressBook({
815
932
  }
816
933
 
817
934
  // src/react/elvix-legal-entities.tsx
818
- import { useEffect as useEffect4, useState as useState14 } from "react";
935
+ import { useEffect as useEffect6, useState as useState15 } from "react";
819
936
  function ElvixLegalEntities({
820
937
  onResult
821
938
  }) {
822
939
  const ctx = useElvixContext();
823
- const [rows, setRows] = useState14(null);
824
- const [error, setError] = useState14(null);
825
- const [busy, setBusy] = useState14(false);
826
- const [adding, setAdding] = useState14(false);
827
- const [form, setForm] = useState14({
940
+ const [rows, setRows] = useState15(null);
941
+ const [error, setError] = useState15(null);
942
+ const [busy, setBusy] = useState15(false);
943
+ const [adding, setAdding] = useState15(false);
944
+ const [form, setForm] = useState15({
828
945
  legalName: "",
829
946
  taxId: "",
830
947
  country: ""
@@ -832,13 +949,13 @@ function ElvixLegalEntities({
832
949
  function reload() {
833
950
  if (!ctx.app) return;
834
951
  fetch(`${ctx.baseUrl}/api/account/apps/${ctx.app.applicationId}/legal-entities`, {
835
- credentials: "include"
952
+ ...authInit()
836
953
  }).then((r) => r.json()).then((j) => {
837
954
  if (j.success && j.data) setRows(j.data.entities);
838
955
  else setError("load_failed");
839
956
  }).catch(() => setError("network"));
840
957
  }
841
- useEffect4(() => {
958
+ useEffect6(() => {
842
959
  reload();
843
960
  }, [ctx.app, ctx.baseUrl]);
844
961
  async function add(e) {
@@ -884,11 +1001,17 @@ export {
884
1001
  ElvixLanguages,
885
1002
  ElvixLeave,
886
1003
  ElvixLegalEntities,
1004
+ ElvixLifecycleWatcher,
887
1005
  ElvixProvider,
888
1006
  ElvixRegion,
889
1007
  ElvixSessions,
890
1008
  ElvixSignIn,
891
1009
  ElvixUsername,
1010
+ getElvixToken,
1011
+ setElvixToken,
892
1012
  useElvixApp,
893
- useElvixContext
1013
+ useElvixContext,
1014
+ useUserMemberships,
1015
+ useUserRoles,
1016
+ useUserScopes
894
1017
  };
package/dist/server.d.ts CHANGED
@@ -7,21 +7,25 @@ import { ElvixVerifyResult } from './index.js';
7
7
  */
8
8
 
9
9
  type VerifyOptions = {
10
- /** Application API key (Console → Credentials). */
11
- apiKey: string;
12
10
  /** Override the elvix origin for testing / proxy setups. */
13
11
  baseUrl?: string;
14
12
  /** Per-request timeout in ms. Default 5000. */
15
13
  timeoutMs?: number;
16
14
  };
17
15
  /**
18
- * Exchange an end-user session token for the verified user envelope
19
- * (roles + scopes + memberships). Hit the `/api/v1/verify` endpoint
20
- * with the customer's Application API key.
16
+ * Verify an end-user session token (the value the SDK handed you via
17
+ * `onSuccess({ token })`) and get back the live user envelope — roles,
18
+ * scopes, memberships — for the token's application.
21
19
  *
22
- * Returns a discriminated union never throws on auth failure.
23
- * Throws only on infra failure (network, timeout, malformed JSON).
20
+ * The token is self-authenticating: POST it as a Bearer to
21
+ * `/api/v1/session`. elvix re-checks the session and the user/membership
22
+ * status on every call, so a banned, paused, or signed-out user verifies as
23
+ * `ok:false` here within one request — call this on each protected request
24
+ * (or cache for a few seconds) and you enforce bans server-side too.
25
+ *
26
+ * Returns a discriminated union — never throws on auth failure. Throws only
27
+ * on infra failure (network, timeout, malformed JSON).
24
28
  */
25
- declare function verifyElvixToken(token: string, opts: VerifyOptions): Promise<ElvixVerifyResult>;
29
+ declare function verifyElvixToken(token: string, opts?: VerifyOptions): Promise<ElvixVerifyResult>;
26
30
 
27
31
  export { type VerifyOptions, verifyElvixToken };
package/dist/server.js CHANGED
@@ -1,33 +1,34 @@
1
1
  // src/server.ts
2
2
  var DEFAULT_BASE_URL = "https://elvix.is";
3
- async function verifyElvixToken(token, opts) {
4
- const url = `${opts.baseUrl ?? DEFAULT_BASE_URL}/api/v1/verify`;
3
+ async function verifyElvixToken(token, opts = {}) {
4
+ const url = `${opts.baseUrl ?? DEFAULT_BASE_URL}/api/v1/session`;
5
5
  const ctrl = new AbortController();
6
6
  const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 5e3);
7
7
  try {
8
8
  const res = await fetch(url, {
9
9
  method: "POST",
10
- headers: {
11
- "content-type": "application/json",
12
- authorization: `Bearer ${opts.apiKey}`
13
- },
14
- body: JSON.stringify({ token }),
10
+ headers: { authorization: `Bearer ${token}` },
15
11
  signal: ctrl.signal
16
12
  });
17
13
  const body = await res.json();
18
- if (!res.ok || !body.success || !body.data) {
14
+ if (!res.ok || !body.ok || !body.userId) {
19
15
  return {
20
16
  ok: false,
21
- error: pickError(body.errorMessage, res.status),
22
- message: body.errorMessage
17
+ error: pickError(body.error, res.status),
18
+ message: body.error
23
19
  };
24
20
  }
25
21
  return {
26
22
  ok: true,
27
- user: body.data.user,
28
- roles: body.data.roles,
29
- scopes: body.data.scopes,
30
- memberships: body.data.memberships
23
+ user: {
24
+ id: body.userId,
25
+ email: body.email ?? "",
26
+ name: body.name ?? void 0,
27
+ avatarUrl: body.avatarUrl ?? void 0
28
+ },
29
+ roles: body.roles ?? [],
30
+ scopes: body.scopes ?? [],
31
+ memberships: body.memberships ?? []
31
32
  };
32
33
  } finally {
33
34
  clearTimeout(timer);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elvix.is/sdk",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Official elvix SDK. Drop-in React components, server helpers, and an MCP server so AI coding agents integrate elvix on the first try.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://elvix.is",
@@ -46,7 +46,11 @@
46
46
  "import": "./dist/mcp/index.js"
47
47
  }
48
48
  },
49
- "files": ["dist", "README.md", "LICENSE"],
49
+ "files": [
50
+ "dist",
51
+ "README.md",
52
+ "LICENSE"
53
+ ],
50
54
  "scripts": {
51
55
  "build": "tsup src/index.ts src/react.ts src/server.ts src/types.ts src/mcp/index.ts src/mcp/bin.ts src/cli/index.ts src/cli/doctor.ts --format esm --dts --clean --external react --external next",
52
56
  "typecheck": "tsc --noEmit",