@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 +49 -1
- package/dist/react.js +200 -77
- package/dist/server.d.ts +12 -8
- package/dist/server.js +15 -14
- package/package.json +6 -2
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
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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] =
|
|
352
|
-
const [busy, setBusy] =
|
|
353
|
-
const [error, setError] =
|
|
354
|
-
const [done, setDone] =
|
|
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
|
|
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] =
|
|
398
|
-
const [error, setError] =
|
|
399
|
-
const [preview, setPreview] =
|
|
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
|
|
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] =
|
|
439
|
-
const [error, setError] =
|
|
440
|
-
const [preview, setPreview] =
|
|
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
|
|
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] =
|
|
482
|
-
const [bio, setBio] =
|
|
483
|
-
const [busy, setBusy] =
|
|
484
|
-
const [error, setError] =
|
|
485
|
-
const [saved, setSaved] =
|
|
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
|
|
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] =
|
|
513
|
-
const [timezone, setTimezone] =
|
|
514
|
-
const [busy, setBusy] =
|
|
515
|
-
const [error, setError] =
|
|
516
|
-
const [saved, setSaved] =
|
|
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
|
|
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] =
|
|
543
|
-
const [busy, setBusy] =
|
|
544
|
-
const [error, setError] =
|
|
545
|
-
const [saved, setSaved] =
|
|
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
|
|
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] =
|
|
572
|
-
const [error, setError] =
|
|
573
|
-
const [busy, setBusy] =
|
|
574
|
-
|
|
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
|
-
|
|
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
|
|
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] =
|
|
604
|
-
const [done, setDone] =
|
|
605
|
-
const [error, setError] =
|
|
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
|
|
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] =
|
|
630
|
-
const [challengeId, setChallengeId] =
|
|
631
|
-
const [code, setCode] =
|
|
632
|
-
const [busy, setBusy] =
|
|
633
|
-
const [error, setError] =
|
|
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
|
|
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] =
|
|
696
|
-
const [challengeId, setChallengeId] =
|
|
697
|
-
const [code, setCode] =
|
|
698
|
-
const [busy, setBusy] =
|
|
699
|
-
const [error, setError] =
|
|
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
|
|
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] =
|
|
762
|
-
const [error, setError] =
|
|
763
|
-
const [busy, setBusy] =
|
|
764
|
-
const [adding, setAdding] =
|
|
765
|
-
const [form, setForm] =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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] =
|
|
824
|
-
const [error, setError] =
|
|
825
|
-
const [busy, setBusy] =
|
|
826
|
-
const [adding, setAdding] =
|
|
827
|
-
const [form, setForm] =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
19
|
-
* (
|
|
20
|
-
*
|
|
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
|
-
*
|
|
23
|
-
*
|
|
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
|
|
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/
|
|
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.
|
|
14
|
+
if (!res.ok || !body.ok || !body.userId) {
|
|
19
15
|
return {
|
|
20
16
|
ok: false,
|
|
21
|
-
error: pickError(body.
|
|
22
|
-
message: body.
|
|
17
|
+
error: pickError(body.error, res.status),
|
|
18
|
+
message: body.error
|
|
23
19
|
};
|
|
24
20
|
}
|
|
25
21
|
return {
|
|
26
22
|
ok: true,
|
|
27
|
-
user:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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.
|
|
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": [
|
|
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",
|