@elvix.is/sdk 0.4.0 → 0.5.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.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,35 @@ 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
+
197
+ // src/react/elvix-sign-in.tsx
169
198
  function ElvixSignIn({
170
199
  onResult,
171
200
  redirectAfterSignIn,
@@ -235,11 +264,13 @@ function ElvixSignIn({
235
264
  if (!res.ok || !body.success) {
236
265
  return fail(body.errorMessage ?? "otp_verify_failed");
237
266
  }
267
+ if (body.data?.token) setElvixToken(body.data.token);
238
268
  setStep("done");
239
269
  onResult?.({
240
270
  ok: true,
241
271
  method: "email_otp",
242
- redirect: body.data?.redirect ?? redirectAfterSignIn
272
+ redirect: body.data?.redirect ?? redirectAfterSignIn,
273
+ token: body.data?.token
243
274
  });
244
275
  } catch (e2) {
245
276
  fail("network", e2 instanceof Error ? e2.message : void 0);
@@ -289,16 +320,90 @@ function ElvixSignIn({
289
320
  ), /* @__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
321
  }
291
322
 
323
+ // src/react/hooks.ts
324
+ import { useCallback, useEffect as useEffect2, useState as useState3 } from "react";
325
+ var POLL_MS = 7e3;
326
+ function useUserList(kind, opts) {
327
+ const { applicationId, baseUrl = "", pollMs = POLL_MS } = opts;
328
+ const [slugs, setSlugs] = useState3([]);
329
+ const [loading, setLoading] = useState3(true);
330
+ const [error, setError] = useState3(null);
331
+ const refresh = useCallback(async () => {
332
+ setError(null);
333
+ try {
334
+ const res = await fetch(
335
+ `${baseUrl}/api/me/${kind}?applicationId=${encodeURIComponent(applicationId)}`,
336
+ authInit()
337
+ );
338
+ const json = await res.json().catch(() => ({}));
339
+ if (!res.ok || json.success === false) {
340
+ setError(json.errorMessage ?? `http_${res.status}`);
341
+ return;
342
+ }
343
+ setSlugs(json.data?.slugs ?? []);
344
+ } catch (e) {
345
+ setError(e instanceof Error ? e.message : "network");
346
+ } finally {
347
+ setLoading(false);
348
+ }
349
+ }, [applicationId, baseUrl, kind]);
350
+ useEffect2(() => {
351
+ refresh();
352
+ const id = setInterval(refresh, pollMs);
353
+ return () => clearInterval(id);
354
+ }, [refresh, pollMs]);
355
+ return { slugs, loading, error, refresh };
356
+ }
357
+ var useUserRoles = (opts) => useUserList("roles", opts);
358
+ var useUserScopes = (opts) => useUserList("scopes", opts);
359
+ var useUserMemberships = (opts) => useUserList("memberships", opts);
360
+
361
+ // src/react/lifecycle-watcher.tsx
362
+ import { useEffect as useEffect3 } from "react";
363
+ function ElvixLifecycleWatcher({
364
+ baseUrl = "",
365
+ pollMs = 7e3,
366
+ onSignedOut
367
+ }) {
368
+ useEffect3(() => {
369
+ let cancelled = false;
370
+ let fired = false;
371
+ const poll = async () => {
372
+ try {
373
+ const res = await fetch(`${baseUrl}/api/v1/session`, { method: "POST", ...authInit() });
374
+ const body = await res.json().catch(() => ({}));
375
+ if (cancelled || fired) return;
376
+ if (!body.ok) {
377
+ fired = true;
378
+ setElvixToken(null);
379
+ const reason = body.error ?? "signed_out";
380
+ if (onSignedOut) onSignedOut(reason);
381
+ else if (typeof window !== "undefined") window.location.reload();
382
+ }
383
+ } catch {
384
+ }
385
+ };
386
+ void poll();
387
+ const id = setInterval(poll, pollMs);
388
+ return () => {
389
+ cancelled = true;
390
+ clearInterval(id);
391
+ };
392
+ }, [baseUrl, pollMs, onSignedOut]);
393
+ return null;
394
+ }
395
+
292
396
  // src/react/elvix-username.tsx
293
- import { useState as useState3 } from "react";
397
+ import { useState as useState4 } from "react";
294
398
 
295
399
  // src/react/lib.ts
296
400
  async function appPost(opts, path, body) {
297
401
  try {
402
+ const auth = authInit();
298
403
  const res = await fetch(`${opts.baseUrl}/api/account/apps/${opts.applicationId}${path}`, {
299
404
  method: "POST",
300
- headers: { "content-type": "application/json" },
301
- credentials: "include",
405
+ headers: { "content-type": "application/json", ...auth.headers },
406
+ credentials: auth.credentials,
302
407
  body: JSON.stringify(body)
303
408
  });
304
409
  const json = await res.json();
@@ -312,10 +417,11 @@ async function appPost(opts, path, body) {
312
417
  }
313
418
  async function appPatch(opts, path, body) {
314
419
  try {
420
+ const auth = authInit();
315
421
  const res = await fetch(`${opts.baseUrl}/api/account/apps/${opts.applicationId}${path}`, {
316
422
  method: "PATCH",
317
- headers: { "content-type": "application/json" },
318
- credentials: "include",
423
+ headers: { "content-type": "application/json", ...auth.headers },
424
+ credentials: auth.credentials,
319
425
  body: JSON.stringify(body)
320
426
  });
321
427
  const json = await res.json();
@@ -329,9 +435,11 @@ async function appPatch(opts, path, body) {
329
435
  }
330
436
  async function appDelete(opts, path) {
331
437
  try {
438
+ const auth = authInit();
332
439
  const res = await fetch(`${opts.baseUrl}/api/account/apps/${opts.applicationId}${path}`, {
333
440
  method: "DELETE",
334
- credentials: "include"
441
+ headers: { ...auth.headers },
442
+ credentials: auth.credentials
335
443
  });
336
444
  const json = await res.json().catch(() => ({}));
337
445
  if (!res.ok || json.success !== void 0 && !json.success) {
@@ -348,10 +456,10 @@ function ElvixUsername({
348
456
  onResult
349
457
  }) {
350
458
  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);
459
+ const [value, setValue] = useState4("");
460
+ const [busy, setBusy] = useState4(false);
461
+ const [error, setError] = useState4(null);
462
+ const [done, setDone] = useState4(null);
355
463
  async function submit(e) {
356
464
  e.preventDefault();
357
465
  if (!ctx.app) return;
@@ -389,14 +497,14 @@ function ElvixUsername({
389
497
  }
390
498
 
391
499
  // src/react/elvix-avatar.tsx
392
- import { useState as useState4 } from "react";
500
+ import { useState as useState5 } from "react";
393
501
  function ElvixAvatar({
394
502
  onResult
395
503
  }) {
396
504
  const ctx = useElvixContext();
397
- const [busy, setBusy] = useState4(false);
398
- const [error, setError] = useState4(null);
399
- const [preview, setPreview] = useState4(null);
505
+ const [busy, setBusy] = useState5(false);
506
+ const [error, setError] = useState5(null);
507
+ const [preview, setPreview] = useState5(null);
400
508
  async function onFile(e) {
401
509
  const file = e.target.files?.[0];
402
510
  if (!file || !ctx.app) return;
@@ -430,14 +538,14 @@ function ElvixAvatar({
430
538
  }
431
539
 
432
540
  // src/react/elvix-banner.tsx
433
- import { useState as useState5 } from "react";
541
+ import { useState as useState6 } from "react";
434
542
  function ElvixBanner({
435
543
  onResult
436
544
  }) {
437
545
  const ctx = useElvixContext();
438
- const [busy, setBusy] = useState5(false);
439
- const [error, setError] = useState5(null);
440
- const [preview, setPreview] = useState5(null);
546
+ const [busy, setBusy] = useState6(false);
547
+ const [error, setError] = useState6(null);
548
+ const [preview, setPreview] = useState6(null);
441
549
  async function onFile(e) {
442
550
  const file = e.target.files?.[0];
443
551
  if (!file || !ctx.app) return;
@@ -471,18 +579,18 @@ function ElvixBanner({
471
579
  }
472
580
 
473
581
  // src/react/elvix-identity-form.tsx
474
- import { useState as useState6 } from "react";
582
+ import { useState as useState7 } from "react";
475
583
  function ElvixIdentityForm({
476
584
  initialName = "",
477
585
  initialBio = "",
478
586
  onResult
479
587
  }) {
480
588
  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);
589
+ const [name, setName] = useState7(initialName);
590
+ const [bio, setBio] = useState7(initialBio);
591
+ const [busy, setBusy] = useState7(false);
592
+ const [error, setError] = useState7(null);
593
+ const [saved, setSaved] = useState7(false);
486
594
  async function submit(e) {
487
595
  e.preventDefault();
488
596
  if (!ctx.app) return;
@@ -502,18 +610,18 @@ function ElvixIdentityForm({
502
610
  }
503
611
 
504
612
  // src/react/elvix-region.tsx
505
- import { useState as useState7 } from "react";
613
+ import { useState as useState8 } from "react";
506
614
  function ElvixRegion({
507
615
  initialCountry = "",
508
616
  initialTimezone = "",
509
617
  onResult
510
618
  }) {
511
619
  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);
620
+ const [country, setCountry] = useState8(initialCountry);
621
+ const [timezone, setTimezone] = useState8(initialTimezone);
622
+ const [busy, setBusy] = useState8(false);
623
+ const [error, setError] = useState8(null);
624
+ const [saved, setSaved] = useState8(false);
517
625
  async function submit(e) {
518
626
  e.preventDefault();
519
627
  if (!ctx.app) return;
@@ -533,16 +641,16 @@ function ElvixRegion({
533
641
  }
534
642
 
535
643
  // src/react/elvix-languages.tsx
536
- import { useState as useState8 } from "react";
644
+ import { useState as useState9 } from "react";
537
645
  function ElvixLanguages({
538
646
  initial = [],
539
647
  onResult
540
648
  }) {
541
649
  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);
650
+ const [raw, setRaw] = useState9(initial.join(", "));
651
+ const [busy, setBusy] = useState9(false);
652
+ const [error, setError] = useState9(null);
653
+ const [saved, setSaved] = useState9(false);
546
654
  async function submit(e) {
547
655
  e.preventDefault();
548
656
  if (!ctx.app) return;
@@ -563,15 +671,15 @@ function ElvixLanguages({
563
671
  }
564
672
 
565
673
  // src/react/elvix-sessions.tsx
566
- import { useEffect as useEffect2, useState as useState9 } from "react";
674
+ import { useEffect as useEffect4, useState as useState10 } from "react";
567
675
  function ElvixSessions({
568
676
  onResult
569
677
  }) {
570
678
  const ctx = useElvixContext();
571
- const [rows, setRows] = useState9(null);
572
- const [error, setError] = useState9(null);
573
- const [busy, setBusy] = useState9(false);
574
- useEffect2(() => {
679
+ const [rows, setRows] = useState10(null);
680
+ const [error, setError] = useState10(null);
681
+ const [busy, setBusy] = useState10(false);
682
+ useEffect4(() => {
575
683
  if (!ctx.app) return;
576
684
  fetch(`${ctx.baseUrl}/api/account/apps/${ctx.app.applicationId}/sessions`, {
577
685
  credentials: "include"
@@ -595,14 +703,14 @@ function ElvixSessions({
595
703
  }
596
704
 
597
705
  // src/react/elvix-export.tsx
598
- import { useState as useState10 } from "react";
706
+ import { useState as useState11 } from "react";
599
707
  function ElvixExport({
600
708
  onResult
601
709
  }) {
602
710
  const ctx = useElvixContext();
603
- const [busy, setBusy] = useState10(false);
604
- const [done, setDone] = useState10(false);
605
- const [error, setError] = useState10(null);
711
+ const [busy, setBusy] = useState11(false);
712
+ const [done, setDone] = useState11(false);
713
+ const [error, setError] = useState11(null);
606
714
  async function start() {
607
715
  if (!ctx.app) return;
608
716
  setBusy(true);
@@ -621,16 +729,16 @@ function ElvixExport({
621
729
  }
622
730
 
623
731
  // src/react/elvix-deactivate.tsx
624
- import { useState as useState11 } from "react";
732
+ import { useState as useState12 } from "react";
625
733
  function ElvixDeactivate({
626
734
  onResult
627
735
  }) {
628
736
  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);
737
+ const [pane, setPane] = useState12("warn");
738
+ const [challengeId, setChallengeId] = useState12(null);
739
+ const [code, setCode] = useState12("");
740
+ const [busy, setBusy] = useState12(false);
741
+ const [error, setError] = useState12(null);
634
742
  async function startChallenge() {
635
743
  if (!ctx.app) return;
636
744
  setBusy(true);
@@ -687,16 +795,16 @@ function ElvixDeactivate({
687
795
  }
688
796
 
689
797
  // src/react/elvix-leave.tsx
690
- import { useState as useState12 } from "react";
798
+ import { useState as useState13 } from "react";
691
799
  function ElvixLeave({
692
800
  onResult
693
801
  }) {
694
802
  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);
803
+ const [pane, setPane] = useState13("warn");
804
+ const [challengeId, setChallengeId] = useState13(null);
805
+ const [code, setCode] = useState13("");
806
+ const [busy, setBusy] = useState13(false);
807
+ const [error, setError] = useState13(null);
700
808
  async function startChallenge() {
701
809
  if (!ctx.app) return;
702
810
  setBusy(true);
@@ -753,16 +861,16 @@ function ElvixLeave({
753
861
  }
754
862
 
755
863
  // src/react/elvix-address-book.tsx
756
- import { useEffect as useEffect3, useState as useState13 } from "react";
864
+ import { useEffect as useEffect5, useState as useState14 } from "react";
757
865
  function ElvixAddressBook({
758
866
  onResult
759
867
  }) {
760
868
  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({
869
+ const [rows, setRows] = useState14(null);
870
+ const [error, setError] = useState14(null);
871
+ const [busy, setBusy] = useState14(false);
872
+ const [adding, setAdding] = useState14(false);
873
+ const [form, setForm] = useState14({
766
874
  label: "Home",
767
875
  line1: "",
768
876
  postalCode: "",
@@ -778,7 +886,7 @@ function ElvixAddressBook({
778
886
  else setError("load_failed");
779
887
  }).catch(() => setError("network"));
780
888
  }
781
- useEffect3(() => {
889
+ useEffect5(() => {
782
890
  reload();
783
891
  }, [ctx.app, ctx.baseUrl]);
784
892
  async function add(e) {
@@ -815,16 +923,16 @@ function ElvixAddressBook({
815
923
  }
816
924
 
817
925
  // src/react/elvix-legal-entities.tsx
818
- import { useEffect as useEffect4, useState as useState14 } from "react";
926
+ import { useEffect as useEffect6, useState as useState15 } from "react";
819
927
  function ElvixLegalEntities({
820
928
  onResult
821
929
  }) {
822
930
  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({
931
+ const [rows, setRows] = useState15(null);
932
+ const [error, setError] = useState15(null);
933
+ const [busy, setBusy] = useState15(false);
934
+ const [adding, setAdding] = useState15(false);
935
+ const [form, setForm] = useState15({
828
936
  legalName: "",
829
937
  taxId: "",
830
938
  country: ""
@@ -838,7 +946,7 @@ function ElvixLegalEntities({
838
946
  else setError("load_failed");
839
947
  }).catch(() => setError("network"));
840
948
  }
841
- useEffect4(() => {
949
+ useEffect6(() => {
842
950
  reload();
843
951
  }, [ctx.app, ctx.baseUrl]);
844
952
  async function add(e) {
@@ -884,11 +992,17 @@ export {
884
992
  ElvixLanguages,
885
993
  ElvixLeave,
886
994
  ElvixLegalEntities,
995
+ ElvixLifecycleWatcher,
887
996
  ElvixProvider,
888
997
  ElvixRegion,
889
998
  ElvixSessions,
890
999
  ElvixSignIn,
891
1000
  ElvixUsername,
1001
+ getElvixToken,
1002
+ setElvixToken,
892
1003
  useElvixApp,
893
- useElvixContext
1004
+ useElvixContext,
1005
+ useUserMemberships,
1006
+ useUserRoles,
1007
+ useUserScopes
894
1008
  };
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.0",
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",