@neetru/sdk 3.4.7 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/ai.cjs +17 -13
  3. package/dist/ai.cjs.map +1 -1
  4. package/dist/ai.d.cts +1 -1
  5. package/dist/ai.d.ts +1 -1
  6. package/dist/ai.mjs +17 -13
  7. package/dist/ai.mjs.map +1 -1
  8. package/dist/auth.cjs +159 -107
  9. package/dist/auth.cjs.map +1 -1
  10. package/dist/auth.d.cts +2 -2
  11. package/dist/auth.d.ts +2 -2
  12. package/dist/auth.mjs +159 -107
  13. package/dist/auth.mjs.map +1 -1
  14. package/dist/catalog.cjs +19 -4
  15. package/dist/catalog.cjs.map +1 -1
  16. package/dist/catalog.d.cts +1 -1
  17. package/dist/catalog.d.ts +1 -1
  18. package/dist/catalog.mjs +19 -4
  19. package/dist/catalog.mjs.map +1 -1
  20. package/dist/checkout.cjs +11 -3
  21. package/dist/checkout.cjs.map +1 -1
  22. package/dist/checkout.d.cts +1 -1
  23. package/dist/checkout.d.ts +1 -1
  24. package/dist/checkout.mjs +11 -3
  25. package/dist/checkout.mjs.map +1 -1
  26. package/dist/db-react.d.cts +1 -1
  27. package/dist/db-react.d.ts +1 -1
  28. package/dist/db.cjs +17 -7
  29. package/dist/db.cjs.map +1 -1
  30. package/dist/db.d.cts +1 -1
  31. package/dist/db.d.ts +1 -1
  32. package/dist/db.mjs +17 -7
  33. package/dist/db.mjs.map +1 -1
  34. package/dist/entitlements.d.cts +1 -1
  35. package/dist/entitlements.d.ts +1 -1
  36. package/dist/firestore-compat.d.cts +1 -1
  37. package/dist/firestore-compat.d.ts +1 -1
  38. package/dist/index.cjs +160 -108
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.d.cts +30 -2
  41. package/dist/index.d.ts +30 -2
  42. package/dist/index.mjs +160 -108
  43. package/dist/index.mjs.map +1 -1
  44. package/dist/mobile.cjs +515 -0
  45. package/dist/mobile.cjs.map +1 -0
  46. package/dist/mobile.d.cts +89 -0
  47. package/dist/mobile.d.ts +89 -0
  48. package/dist/mobile.mjs +513 -0
  49. package/dist/mobile.mjs.map +1 -0
  50. package/dist/mocks.cjs +17 -0
  51. package/dist/mocks.cjs.map +1 -1
  52. package/dist/mocks.d.cts +5 -1
  53. package/dist/mocks.d.ts +5 -1
  54. package/dist/mocks.mjs +17 -0
  55. package/dist/mocks.mjs.map +1 -1
  56. package/dist/notifications.cjs +11 -3
  57. package/dist/notifications.cjs.map +1 -1
  58. package/dist/notifications.d.cts +1 -1
  59. package/dist/notifications.d.ts +1 -1
  60. package/dist/notifications.mjs +11 -3
  61. package/dist/notifications.mjs.map +1 -1
  62. package/dist/react.cjs +36 -2
  63. package/dist/react.cjs.map +1 -1
  64. package/dist/react.d.cts +1 -1
  65. package/dist/react.d.ts +1 -1
  66. package/dist/react.mjs +36 -2
  67. package/dist/react.mjs.map +1 -1
  68. package/dist/support.cjs +15 -4
  69. package/dist/support.cjs.map +1 -1
  70. package/dist/support.d.cts +1 -1
  71. package/dist/support.d.ts +1 -1
  72. package/dist/support.mjs +15 -4
  73. package/dist/support.mjs.map +1 -1
  74. package/dist/telemetry.cjs +11 -3
  75. package/dist/telemetry.cjs.map +1 -1
  76. package/dist/telemetry.d.cts +1 -1
  77. package/dist/telemetry.d.ts +1 -1
  78. package/dist/telemetry.mjs +11 -3
  79. package/dist/telemetry.mjs.map +1 -1
  80. package/dist/{types-nnztVk7G.d.ts → types-BPATkXYu.d.ts} +35 -7
  81. package/dist/{types-DIVZl7nO.d.cts → types-Du_iioJ7.d.cts} +35 -7
  82. package/dist/usage.cjs +11 -3
  83. package/dist/usage.cjs.map +1 -1
  84. package/dist/usage.d.cts +1 -1
  85. package/dist/usage.d.ts +1 -1
  86. package/dist/usage.mjs +11 -3
  87. package/dist/usage.mjs.map +1 -1
  88. package/dist/webhooks.cjs +17 -4
  89. package/dist/webhooks.cjs.map +1 -1
  90. package/dist/webhooks.d.cts +1 -1
  91. package/dist/webhooks.d.ts +1 -1
  92. package/dist/webhooks.mjs +17 -4
  93. package/dist/webhooks.mjs.map +1 -1
  94. package/package.json +9 -1
@@ -0,0 +1,513 @@
1
+ // src/errors.ts
2
+ var NeetruError = class _NeetruError extends Error {
3
+ code;
4
+ status;
5
+ requestId;
6
+ /**
7
+ * Motivo semântico opcional propagado pelo Core no corpo do erro (campo
8
+ * `reason` no TOPO do body, fora de `error`). Usado principalmente pelo 503
9
+ * de lease de DB, onde `reason` discrimina o estado do ambiente
10
+ * (`suspended` | `archived` | `revoked` | `provisioning` | `degraded` |
11
+ * `no_route` | `expired`). Camadas superiores (ex.: `db/sql`) mapeiam isso
12
+ * para um `NeetruDbErrorCode` mais específico (recuperável vs não).
13
+ */
14
+ reason;
15
+ constructor(code, message, status, requestId, reason) {
16
+ super(message);
17
+ this.name = "NeetruError";
18
+ this.code = code;
19
+ this.status = status;
20
+ this.requestId = requestId;
21
+ this.reason = reason;
22
+ Object.setPrototypeOf(this, _NeetruError.prototype);
23
+ }
24
+ };
25
+
26
+ // src/types.ts
27
+ var DEFAULT_BASE_URL = "https://api.neetru.com";
28
+
29
+ // src/base-url-allowlist.ts
30
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
31
+ var NEETRU_SUFFIX = ".neetru.com";
32
+ var NEETRU_APEX = "neetru.com";
33
+ var SAFE_DEV_HOST_RE = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?\.(test|localhost)$/;
34
+ function isSafeDevHost(hostname) {
35
+ return SAFE_DEV_HOST_RE.test(hostname);
36
+ }
37
+ function normalizeHostname(hostname) {
38
+ return hostname.toLowerCase().replace(/\.+$/, "");
39
+ }
40
+ function validateBaseUrl(baseUrl, allowedHosts = []) {
41
+ if (typeof baseUrl !== "string" || baseUrl.length === 0) {
42
+ throw new NeetruError("invalid_config", "baseUrl is required");
43
+ }
44
+ let parsed;
45
+ try {
46
+ parsed = new URL(baseUrl);
47
+ } catch {
48
+ throw new NeetruError("invalid_config", `baseUrl is not a valid URL: ${baseUrl}`);
49
+ }
50
+ const protocol = parsed.protocol;
51
+ const hostname = normalizeHostname(parsed.hostname);
52
+ if (isInsecureBypassActive()) {
53
+ if (typeof console !== "undefined") {
54
+ console.warn(
55
+ `[neetru-sdk] NEETRU_ALLOW_INSECURE_BASEURL=1 ativo. baseUrl=${baseUrl} aceito sem valida\xE7\xE3o.`
56
+ );
57
+ }
58
+ return baseUrl.replace(/\/+$/, "");
59
+ }
60
+ if (protocol !== "https:" && protocol !== "http:") {
61
+ throw new NeetruError(
62
+ "invalid_config",
63
+ `baseUrl protocol must be http: or https:, got ${protocol}`
64
+ );
65
+ }
66
+ if (protocol === "http:") {
67
+ const isLoopback = LOOPBACK_HOSTS.has(hostname) || isSafeDevHost(hostname);
68
+ if (!isLoopback) {
69
+ throw new NeetruError(
70
+ "invalid_config",
71
+ `baseUrl with http: only allowed for localhost/127.0.0.1/*.test/*.localhost. Got: ${hostname}. Use https: or set NEETRU_ALLOW_INSECURE_BASEURL=1 to bypass.`
72
+ );
73
+ }
74
+ return baseUrl.replace(/\/+$/, "");
75
+ }
76
+ const isNeetruDomain = hostname === NEETRU_APEX || hostname.endsWith(NEETRU_SUFFIX);
77
+ const isAllowedHost = allowedHosts.some((allowed) => {
78
+ const norm = allowed.toLowerCase();
79
+ return hostname === norm || hostname.endsWith(`.${norm}`);
80
+ });
81
+ const isLoopbackHttps = LOOPBACK_HOSTS.has(hostname) || isSafeDevHost(hostname);
82
+ if (!isNeetruDomain && !isAllowedHost && !isLoopbackHttps) {
83
+ throw new NeetruError(
84
+ "invalid_config",
85
+ `baseUrl ${hostname} is not in the allowlist. Allowed: *.neetru.com, localhost, or configure { allowedHosts: ['...'] }. Bypass: NEETRU_ALLOW_INSECURE_BASEURL=1.`
86
+ );
87
+ }
88
+ return baseUrl.replace(/\/+$/, "");
89
+ }
90
+ function isInsecureBypassActive() {
91
+ try {
92
+ const proc = globalThis.process;
93
+ const envVal = proc?.env?.NEETRU_ALLOW_INSECURE_BASEURL;
94
+ if (envVal === "1" || envVal === "true") return true;
95
+ } catch {
96
+ }
97
+ try {
98
+ const g = globalThis.NEETRU_ALLOW_INSECURE_BASEURL;
99
+ if (g === "1" || g === "true" || g === true) return true;
100
+ } catch {
101
+ }
102
+ return false;
103
+ }
104
+
105
+ // src/api-key.ts
106
+ var API_KEY_RE = /^nrt_([A-Za-z0-9]+)_([A-Za-z0-9]+)$/;
107
+ function parseApiKey(apiKey) {
108
+ if (typeof apiKey !== "string" || apiKey.length === 0) {
109
+ throw new NeetruError(
110
+ "invalid_config",
111
+ "apiKey is required (formato nrt_<keyId>_<secret>)"
112
+ );
113
+ }
114
+ const match = API_KEY_RE.exec(apiKey);
115
+ if (!match) {
116
+ throw new NeetruError(
117
+ "invalid_config",
118
+ "apiKey malformado - esperado formato nrt_<keyId>_<secret>"
119
+ );
120
+ }
121
+ return { keyId: match[1], secret: match[2] };
122
+ }
123
+
124
+ // src/auth-pkce-shared.ts
125
+ function getWebCrypto() {
126
+ const c = globalThis.crypto;
127
+ if (!c || typeof c.getRandomValues !== "function" || !c.subtle) {
128
+ throw new NeetruError(
129
+ "runtime_unsupported",
130
+ "auth.signIn requer WebCrypto (crypto.subtle) \u2014 indispon\xEDvel neste runtime. Use HTTPS (ou http://localhost) num browser moderno, ou instale um polyfill WebCrypto (ex: react-native-quick-crypto) em React Native."
131
+ );
132
+ }
133
+ return c;
134
+ }
135
+ function base64UrlFromBytes(bytes) {
136
+ let str = "";
137
+ for (const b of bytes) str += String.fromCharCode(b);
138
+ const b64 = typeof btoa === "function" ? btoa(str) : Buffer.from(str, "binary").toString("base64");
139
+ return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
140
+ }
141
+ function randomUrlSafe(crypto, byteLen) {
142
+ const bytes = new Uint8Array(byteLen);
143
+ crypto.getRandomValues(bytes);
144
+ return base64UrlFromBytes(bytes);
145
+ }
146
+ async function pkceChallengeS256(crypto, verifier) {
147
+ const data = new TextEncoder().encode(verifier);
148
+ const digest = await crypto.subtle.digest("SHA-256", data);
149
+ return base64UrlFromBytes(new Uint8Array(digest));
150
+ }
151
+ function resolveIdpOrigin(baseUrl) {
152
+ const overrideAuthUrl = typeof globalThis.NEETRU_AUTH_URL === "string" ? globalThis.NEETRU_AUTH_URL : null;
153
+ return overrideAuthUrl ?? baseUrl.replace(/^https?:\/\/api\./, "https://auth.");
154
+ }
155
+ function resolveOidcClientId(config) {
156
+ let clientId = config.oidcClientId;
157
+ if (!clientId && config.apiKey) {
158
+ clientId = parseApiKey(config.apiKey).keyId;
159
+ }
160
+ if (!clientId) {
161
+ throw new NeetruError(
162
+ "invalid_config",
163
+ "auth OIDC requer `oidcClientId` (ou `apiKey`) configurado para montar o client_id."
164
+ );
165
+ }
166
+ return clientId;
167
+ }
168
+ function buildAuthorizeUrl(params) {
169
+ const url = new URL("/api/v1/oauth/authorize", params.idpOrigin);
170
+ url.searchParams.set("response_type", "code");
171
+ url.searchParams.set("client_id", params.clientId);
172
+ url.searchParams.set("redirect_uri", params.redirectUri);
173
+ url.searchParams.set("scope", params.scope);
174
+ url.searchParams.set("state", params.state);
175
+ url.searchParams.set("nonce", params.nonce);
176
+ url.searchParams.set("code_challenge", params.codeChallenge);
177
+ url.searchParams.set("code_challenge_method", "S256");
178
+ return url;
179
+ }
180
+ var OAUTH_TOKEN_PATH = "/api/v1/oauth/token";
181
+ function decodeJwtPayload(token) {
182
+ const parts = token.split(".");
183
+ if (parts.length !== 3) return null;
184
+ try {
185
+ const b64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
186
+ const pad = "=".repeat((4 - b64.length % 4) % 4);
187
+ const decoded = typeof atob === "function" ? atob(b64 + pad) : Buffer.from(b64 + pad, "base64").toString("utf-8");
188
+ return JSON.parse(decoded);
189
+ } catch {
190
+ return null;
191
+ }
192
+ }
193
+ function jwtToNeetruUser(token) {
194
+ const payload = decodeJwtPayload(token);
195
+ if (!payload || typeof payload.sub !== "string") return null;
196
+ if (typeof payload.exp === "number" && payload.exp * 1e3 < Date.now()) {
197
+ return null;
198
+ }
199
+ return {
200
+ uid: payload.sub,
201
+ email: typeof payload.email === "string" ? payload.email : "",
202
+ emailVerified: typeof payload.email_verified === "boolean" ? payload.email_verified : void 0,
203
+ displayName: typeof payload.name === "string" ? payload.name : void 0,
204
+ photoURL: typeof payload.picture === "string" ? payload.picture : void 0,
205
+ isStaff: payload.is_staff === true,
206
+ isCustomer: payload.is_customer === true,
207
+ tenantId: typeof payload.tenant_id === "string" ? payload.tenant_id : void 0,
208
+ ...payload
209
+ };
210
+ }
211
+
212
+ // src/mobile.ts
213
+ var OIDC_TX_KEY = "neetru_oauth_tx";
214
+ var ID_TOKEN_KEY = "neetru_id_token";
215
+ var REFRESH_TOKEN_KEY = "neetru_refresh_token";
216
+ function resolveMobileConfig(config) {
217
+ if (!config.storage) {
218
+ throw new NeetruError(
219
+ "invalid_config",
220
+ "createMobileAuthNamespace requer `storage` (MobileAuthStorage) \u2014 ver JSDoc de src/mobile.ts requisito 4."
221
+ );
222
+ }
223
+ if (typeof config.openAuthUrl !== "function") {
224
+ throw new NeetruError(
225
+ "invalid_config",
226
+ "createMobileAuthNamespace requer `openAuthUrl` (browser externo, NUNCA WebView embutida) \u2014 ver JSDoc de src/mobile.ts requisito 3."
227
+ );
228
+ }
229
+ const fetchImpl = config.fetch ?? globalThis.fetch;
230
+ if (typeof fetchImpl !== "function") {
231
+ throw new NeetruError(
232
+ "invalid_config",
233
+ "fetch is not available in this runtime. Pass `fetch` explicitly to createMobileAuthNamespace."
234
+ );
235
+ }
236
+ const baseUrl = validateBaseUrl(config.baseUrl ?? DEFAULT_BASE_URL, config.allowedHosts ?? []);
237
+ return {
238
+ fetchImpl: fetchImpl.bind(globalThis),
239
+ baseUrl,
240
+ oidcClientId: config.oidcClientId,
241
+ apiKey: config.apiKey,
242
+ storage: config.storage,
243
+ openAuthUrl: config.openAuthUrl
244
+ };
245
+ }
246
+ async function postToken(fetchImpl, tokenUrl, form) {
247
+ let res;
248
+ try {
249
+ res = await fetchImpl(tokenUrl, {
250
+ method: "POST",
251
+ headers: { "content-type": "application/x-www-form-urlencoded" },
252
+ body: form.toString()
253
+ });
254
+ } catch (err) {
255
+ throw new NeetruError(
256
+ "network_error",
257
+ `POST ${tokenUrl} falhou (network): ${err?.message ?? String(err)}`
258
+ );
259
+ }
260
+ let payload = null;
261
+ try {
262
+ payload = await res.json();
263
+ } catch {
264
+ payload = null;
265
+ }
266
+ if (!res.ok || typeof payload?.error === "string") {
267
+ const desc = typeof payload?.error_description === "string" && payload.error_description || typeof payload?.error === "string" && payload.error || `HTTP ${res.status}`;
268
+ throw new NeetruError(
269
+ "token_exchange_failed",
270
+ `Troca de tokens falhou no /token: ${desc}`,
271
+ res.status
272
+ );
273
+ }
274
+ return payload ?? {};
275
+ }
276
+ function createMobileAuthNamespace(config) {
277
+ const resolved = resolveMobileConfig(config);
278
+ const listeners = /* @__PURE__ */ new Set();
279
+ function tokenUrl() {
280
+ return `${resolveIdpOrigin(resolved.baseUrl)}${OAUTH_TOKEN_PATH}`;
281
+ }
282
+ async function notify() {
283
+ const user = await getUser();
284
+ for (const l of listeners) {
285
+ try {
286
+ l(user);
287
+ } catch {
288
+ }
289
+ }
290
+ }
291
+ async function signIn(options) {
292
+ if (!options?.redirectUri) {
293
+ throw new NeetruError(
294
+ "invalid_config",
295
+ "mobile auth.signIn requer `redirectUri` (Universal Link/App Link https://... registrado no Core) \u2014 n\xE3o h\xE1 location.origin impl\xEDcito em React Native."
296
+ );
297
+ }
298
+ const crypto = getWebCrypto();
299
+ const scope = options.scope ?? "openid profile email";
300
+ const clientId = resolveOidcClientId(resolved);
301
+ const state = randomUrlSafe(crypto, 16);
302
+ const nonce = randomUrlSafe(crypto, 16);
303
+ const codeVerifier = randomUrlSafe(crypto, 32);
304
+ const codeChallenge = await pkceChallengeS256(crypto, codeVerifier);
305
+ const tx = {
306
+ state,
307
+ nonce,
308
+ codeVerifier,
309
+ redirectUri: options.redirectUri,
310
+ postLoginRedirect: options.postLoginRedirect
311
+ };
312
+ await resolved.storage.setItem(OIDC_TX_KEY, JSON.stringify(tx));
313
+ const idpOrigin = resolveIdpOrigin(resolved.baseUrl);
314
+ const url = buildAuthorizeUrl({
315
+ idpOrigin,
316
+ clientId,
317
+ redirectUri: options.redirectUri,
318
+ scope,
319
+ state,
320
+ nonce,
321
+ codeChallenge
322
+ });
323
+ await resolved.openAuthUrl(url.toString());
324
+ }
325
+ async function completeSignIn(callbackUrl) {
326
+ if (!callbackUrl || typeof callbackUrl !== "string") {
327
+ throw new NeetruError(
328
+ "invalid_config",
329
+ "mobile auth.completeSignIn requer `callbackUrl` (a URL completa do deep link de retorno) \u2014 mobile n\xE3o tem location.href impl\xEDcito."
330
+ );
331
+ }
332
+ let parsed;
333
+ try {
334
+ parsed = new URL(callbackUrl);
335
+ } catch {
336
+ throw new NeetruError("invalid_config", `callbackUrl n\xE3o \xE9 uma URL v\xE1lida: ${callbackUrl}`);
337
+ }
338
+ const params = parsed.searchParams;
339
+ const idpError = params.get("error");
340
+ if (idpError) {
341
+ const desc = params.get("error_description") ?? idpError;
342
+ await resolved.storage.removeItem(OIDC_TX_KEY);
343
+ throw new NeetruError(idpError, `OIDC authorize falhou: ${desc}`);
344
+ }
345
+ const code = params.get("code");
346
+ if (!code) {
347
+ await resolved.storage.removeItem(OIDC_TX_KEY);
348
+ throw new NeetruError(
349
+ "invalid_config",
350
+ "callbackUrl n\xE3o cont\xE9m `?code=` nem `?error=` \u2014 n\xE3o \xE9 um callback OIDC v\xE1lido."
351
+ );
352
+ }
353
+ const returnedState = params.get("state");
354
+ const txRaw = await resolved.storage.getItem(OIDC_TX_KEY);
355
+ let tx = null;
356
+ if (txRaw) {
357
+ try {
358
+ tx = JSON.parse(txRaw);
359
+ } catch {
360
+ tx = null;
361
+ }
362
+ }
363
+ if (!tx) {
364
+ throw new NeetruError(
365
+ "oidc_state_mismatch",
366
+ "Callback OIDC sem transa\xE7\xE3o pendente \u2014 `signIn` n\xE3o foi iniciado (ou j\xE1 consumido)."
367
+ );
368
+ }
369
+ if (!returnedState || returnedState !== tx.state) {
370
+ await resolved.storage.removeItem(OIDC_TX_KEY);
371
+ throw new NeetruError(
372
+ "oidc_state_mismatch",
373
+ "state do callback OIDC n\xE3o confere com o da transa\xE7\xE3o (poss\xEDvel CSRF)."
374
+ );
375
+ }
376
+ await resolved.storage.removeItem(OIDC_TX_KEY);
377
+ const clientId = resolveOidcClientId(resolved);
378
+ const form = new URLSearchParams({
379
+ grant_type: "authorization_code",
380
+ code,
381
+ redirect_uri: tx.redirectUri,
382
+ client_id: clientId,
383
+ code_verifier: tx.codeVerifier
384
+ });
385
+ const payload = await postToken(resolved.fetchImpl, tokenUrl(), form);
386
+ const idToken = payload.id_token;
387
+ if (typeof idToken !== "string" || idToken.length === 0) {
388
+ throw new NeetruError(
389
+ "token_exchange_failed",
390
+ "Resposta do /token sem `id_token` \u2014 o login n\xE3o pode ser completado."
391
+ );
392
+ }
393
+ const claims = decodeJwtPayload(idToken);
394
+ if (!claims) {
395
+ throw new NeetruError(
396
+ "token_exchange_failed",
397
+ "id_token retornado pelo /token \xE9 malformado (payload indecodific\xE1vel)."
398
+ );
399
+ }
400
+ if (claims.nonce !== tx.nonce) {
401
+ throw new NeetruError(
402
+ "oidc_nonce_mismatch",
403
+ "nonce do id_token n\xE3o confere com o da transa\xE7\xE3o OIDC (poss\xEDvel replay). Token descartado."
404
+ );
405
+ }
406
+ const user = jwtToNeetruUser(idToken);
407
+ if (!user) {
408
+ throw new NeetruError(
409
+ "token_exchange_failed",
410
+ "id_token retornado pelo /token n\xE3o tem `sub` ou j\xE1 est\xE1 expirado."
411
+ );
412
+ }
413
+ await resolved.storage.setItem(ID_TOKEN_KEY, idToken);
414
+ const refreshToken = payload.refresh_token;
415
+ if (typeof refreshToken === "string" && refreshToken.length > 0) {
416
+ await resolved.storage.setItem(REFRESH_TOKEN_KEY, refreshToken);
417
+ }
418
+ await notify();
419
+ return { user, postLoginRedirect: tx.postLoginRedirect };
420
+ }
421
+ async function getIdToken() {
422
+ const token = await resolved.storage.getItem(ID_TOKEN_KEY);
423
+ if (!token) return null;
424
+ return jwtToNeetruUser(token) ? token : null;
425
+ }
426
+ async function getUser() {
427
+ const token = await resolved.storage.getItem(ID_TOKEN_KEY);
428
+ if (!token) return null;
429
+ return jwtToNeetruUser(token);
430
+ }
431
+ async function refreshTokens() {
432
+ const refreshToken = await resolved.storage.getItem(REFRESH_TOKEN_KEY);
433
+ if (!refreshToken) return false;
434
+ let clientId;
435
+ try {
436
+ clientId = resolveOidcClientId(resolved);
437
+ } catch {
438
+ return false;
439
+ }
440
+ const form = new URLSearchParams({
441
+ grant_type: "refresh_token",
442
+ refresh_token: refreshToken,
443
+ client_id: clientId
444
+ });
445
+ let payload;
446
+ try {
447
+ payload = await postToken(resolved.fetchImpl, tokenUrl(), form);
448
+ } catch {
449
+ return false;
450
+ }
451
+ const idToken = payload.id_token;
452
+ if (typeof idToken !== "string" || idToken.length === 0) return false;
453
+ const user = jwtToNeetruUser(idToken);
454
+ if (!user) return false;
455
+ await resolved.storage.setItem(ID_TOKEN_KEY, idToken);
456
+ const rotated = payload.refresh_token;
457
+ if (typeof rotated === "string" && rotated.length > 0) {
458
+ await resolved.storage.setItem(REFRESH_TOKEN_KEY, rotated);
459
+ }
460
+ await notify();
461
+ return true;
462
+ }
463
+ async function signOut() {
464
+ const storedToken = await resolved.storage.getItem(ID_TOKEN_KEY);
465
+ await resolved.storage.removeItem(ID_TOKEN_KEY);
466
+ await resolved.storage.removeItem(REFRESH_TOKEN_KEY);
467
+ if (storedToken) {
468
+ const idpOrigin = resolveIdpOrigin(resolved.baseUrl);
469
+ const revokeUrl = `${idpOrigin}/api/v1/oauth/revoke`;
470
+ try {
471
+ await resolved.fetchImpl(revokeUrl, {
472
+ method: "POST",
473
+ headers: { "content-type": "application/json" },
474
+ body: JSON.stringify({ token: storedToken })
475
+ });
476
+ } catch (err) {
477
+ if (typeof console !== "undefined") {
478
+ console.warn(
479
+ "[neetru-sdk] mobile signOut: server-side revoke falhou. Refresh token pode estar stale.",
480
+ err
481
+ );
482
+ }
483
+ }
484
+ }
485
+ await notify();
486
+ }
487
+ return {
488
+ signIn,
489
+ completeSignIn,
490
+ getIdToken,
491
+ getUser,
492
+ onAuthStateChanged(listener) {
493
+ listeners.add(listener);
494
+ void getUser().then((user) => {
495
+ if (listeners.has(listener)) {
496
+ try {
497
+ listener(user);
498
+ } catch {
499
+ }
500
+ }
501
+ });
502
+ return () => {
503
+ listeners.delete(listener);
504
+ };
505
+ },
506
+ refreshTokens,
507
+ signOut
508
+ };
509
+ }
510
+
511
+ export { createMobileAuthNamespace };
512
+ //# sourceMappingURL=mobile.mjs.map
513
+ //# sourceMappingURL=mobile.mjs.map