@neetru/sdk 2.1.1 → 2.3.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/CHANGELOG.md +120 -1
- package/dist/auth.cjs +265 -6
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +4 -2
- package/dist/auth.d.ts +4 -2
- package/dist/auth.mjs +265 -7
- package/dist/auth.mjs.map +1 -1
- package/dist/catalog.cjs.map +1 -1
- package/dist/catalog.d.cts +1 -1
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.mjs.map +1 -1
- package/dist/checkout.cjs.map +1 -1
- package/dist/checkout.d.cts +1 -1
- package/dist/checkout.d.ts +1 -1
- package/dist/checkout.mjs.map +1 -1
- package/dist/db.cjs +138 -6
- package/dist/db.cjs.map +1 -1
- package/dist/db.d.cts +1 -1
- package/dist/db.d.ts +1 -1
- package/dist/db.mjs +138 -6
- package/dist/db.mjs.map +1 -1
- package/dist/entitlements.cjs.map +1 -1
- package/dist/entitlements.d.cts +1 -1
- package/dist/entitlements.d.ts +1 -1
- package/dist/entitlements.mjs.map +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.mjs.map +1 -1
- package/dist/index.cjs +261 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +261 -6
- package/dist/index.mjs.map +1 -1
- package/dist/mocks.cjs +55 -0
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.cts +21 -1
- package/dist/mocks.d.ts +21 -1
- package/dist/mocks.mjs +55 -0
- package/dist/mocks.mjs.map +1 -1
- package/dist/notifications.cjs.map +1 -1
- package/dist/notifications.d.cts +1 -1
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.mjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/support.cjs.map +1 -1
- package/dist/support.d.cts +1 -1
- package/dist/support.d.ts +1 -1
- package/dist/support.mjs.map +1 -1
- package/dist/telemetry.cjs.map +1 -1
- package/dist/telemetry.d.cts +1 -1
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.mjs.map +1 -1
- package/dist/{types-Cfb-qeDg.d.cts → types-B6YJrynl.d.cts} +91 -13
- package/dist/{types-V1EfjR1p.d.ts → types-DvhtZ4bi.d.ts} +91 -13
- package/dist/usage.cjs.map +1 -1
- package/dist/usage.d.cts +1 -1
- package/dist/usage.d.ts +1 -1
- package/dist/usage.mjs.map +1 -1
- package/dist/webhooks.cjs.map +1 -1
- package/dist/webhooks.d.cts +1 -1
- package/dist/webhooks.d.ts +1 -1
- package/dist/webhooks.mjs.map +1 -1
- package/package.json +134 -133
package/CHANGELOG.md
CHANGED
|
@@ -5,9 +5,128 @@ All notable changes to `@neetru/sdk` will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.3.0] - 2026-05-25 — **feat(db-config): renomeia engine→transport com deprecation — bug_ed9da7fd [HIGH]**
|
|
9
|
+
|
|
10
|
+
Fix do bug `bug_ed9da7fd11194eac87f0eb9c75e5fad9` (HIGH) — colisão de terminologia: `engine` no SDK
|
|
11
|
+
configurava o transporte SDK↔Core (REST/Firestore/WebSocket), mas `engine` no admin database (CLI
|
|
12
|
+
`neetru admin database create --engine=...`) configura o storage engine (firestore-instance,
|
|
13
|
+
cloud-sql-postgres, vm-postgres-single…). Devs colocavam `firestore-instance` no SDK e quebravam.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`NeetruDbTransport`** — novo tipo público canônico para o campo de transporte SDK↔Core.
|
|
18
|
+
Valores: `'rest' | 'firestore' | 'nosql-vm'` (idênticos aos de `NeetruDbEngine`).
|
|
19
|
+
|
|
20
|
+
- **`NeetruDbOptions.transport`** — novo campo canônico que substitui `engine`.
|
|
21
|
+
```ts
|
|
22
|
+
// v2.3+ (preferido):
|
|
23
|
+
createNeetruClient({ db: { transport: 'rest' } });
|
|
24
|
+
createNeetruClient({ db: { transport: 'firestore' } });
|
|
25
|
+
createNeetruClient({ db: { transport: 'nosql-vm', realtimeGatewayUrl: '...' } });
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Deprecated
|
|
29
|
+
|
|
30
|
+
- **`NeetruDbOptions.engine`** — depreciado em v2.3, será removido em v3.0.
|
|
31
|
+
Continua funcionando: se `transport` estiver ausente e `engine` presente, o valor de `engine`
|
|
32
|
+
é usado como transport com `console.warn` de deprecação.
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
// v2.x (depreciado — emite console.warn):
|
|
36
|
+
createNeetruClient({ db: { engine: 'firestore' } });
|
|
37
|
+
// ⚠ [neetru/sdk] NeetruDbOptions.engine está depreciado...
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- **`NeetruDbEngine`** — depreciado em v2.3, será removido em v3.0.
|
|
41
|
+
Agora é alias de `NeetruDbTransport`. Use `NeetruDbTransport` em código novo.
|
|
42
|
+
|
|
43
|
+
### Behavior
|
|
44
|
+
|
|
45
|
+
- `transport` e `engine` podem coexistir: `transport` sempre prevalece, sem warn.
|
|
46
|
+
- Sem `transport` nem `engine`: default `'rest'`, sem warn (comportamento idêntico ao v2.x).
|
|
47
|
+
- A relação 1:N entre storage engines e transports é documentada no JSDoc de `NeetruDbTransport`:
|
|
48
|
+
qualquer storage engine (cloud-sql-postgres, vm-postgres-single…) pode ser acessado com
|
|
49
|
+
`transport: 'rest'`; `transport: 'firestore'` e `transport: 'nosql-vm'` são para bancos
|
|
50
|
+
documentais com realtime nativo.
|
|
51
|
+
|
|
52
|
+
### Tests
|
|
53
|
+
|
|
54
|
+
- 5 testes novos em `src/__tests__/db-real.test.ts`:
|
|
55
|
+
`transport=firestore`, `transport=nosql-vm`, `transport=rest`, colisão `transport+engine`
|
|
56
|
+
(transport prevalece, sem warn), default sem campos (sem warn).
|
|
57
|
+
- Testes de regressão: `engine=firestore` e `engine=nosql-vm` legados ainda passam + emitem warn.
|
|
58
|
+
|
|
59
|
+
## [2.2.1] - 2026-05-25 — **fix(offline-docs): fallback MemoryStore em Node — bug_ba287a CRITICAL**
|
|
60
|
+
|
|
61
|
+
Fix do bug `bug_ba287aefdf6c4327b026d588a239ad06` (CRITICAL) — `client.db.collection().set/add/update`
|
|
62
|
+
lançava `ReferenceError: indexedDB is not defined` em Node.js (reportado por pdv-agiliza prod).
|
|
63
|
+
|
|
64
|
+
### Fixed
|
|
65
|
+
|
|
66
|
+
- **`createOfflineDocumentsNamespace` — auto-detect Node runtime e usa `MemoryStore`**.
|
|
67
|
+
A função agora verifica `typeof indexedDB === 'undefined' || typeof window === 'undefined'`
|
|
68
|
+
antes de instanciar `LocalStore` (que depende de `idb` / IndexedDB). Em Node/Bun/Edge Workers
|
|
69
|
+
sem IDB, cai automaticamente em `MemoryStore` — implementação in-process com a mesma API,
|
|
70
|
+
sem nenhuma flag explícita necessária. Ops de documentos (`set/add/update/remove/get/list`,
|
|
71
|
+
`batch`, `onSnapshot`, `onDoc`) funcionam normalmente em Node; dados não persistem entre
|
|
72
|
+
reinicializações do processo (comportamento esperado em SSR/server-side).
|
|
73
|
+
|
|
74
|
+
### Added
|
|
75
|
+
|
|
76
|
+
- **`src/db/offline/memory-store.ts`** — `MemoryStore`: implementação in-memory do contrato
|
|
77
|
+
público de `LocalStore`. Cobre todas as 5 stores (documents, mutations, query\_cache,
|
|
78
|
+
sync\_meta, conflict\_log) com Maps/Arrays em memória.
|
|
79
|
+
|
|
80
|
+
### Tests
|
|
81
|
+
|
|
82
|
+
- **`src/__tests__/offline-docs-node-compat.test.ts`** — 5 casos de regressão:
|
|
83
|
+
verifica que a stack offline não lança `ReferenceError` em ambiente sem IDB,
|
|
84
|
+
e que `set/add/update` + `get` roundtrips funcionam in-memory.
|
|
85
|
+
|
|
86
|
+
## [2.2.0] - 2026-05-25 — **auth.verifyToken — validação server-side de id_token via JWKS [feature]**
|
|
87
|
+
|
|
88
|
+
Fix do bug `bug_a7ef1702680044e3994b74bdccecb2ab` — resolve P0-1 audit security pdv-agiliza.
|
|
89
|
+
|
|
90
|
+
### Added
|
|
91
|
+
|
|
92
|
+
- **`client.auth.verifyToken(token, options?): Promise<NeetruUser>`** — valida um id_token JWT
|
|
93
|
+
emitido por `auth.neetru.com` server-side, sem round-trip de rede por request. Usa JWKS local
|
|
94
|
+
cacheado (default TTL 1h, configurável via `options.jwksCacheTtlMs`).
|
|
95
|
+
|
|
96
|
+
Erros tipados novos (`NeetruErrorCode`):
|
|
97
|
+
- `token_expired` — `exp` no passado
|
|
98
|
+
- `token_invalid_signature` — assinatura não verificada pela JWKS
|
|
99
|
+
- `token_invalid_audience` — `aud` não bate com o `clientId` extraído do `apiKey`
|
|
100
|
+
- `token_invalid_issuer` — `iss` não é `https://auth.neetru.com`
|
|
101
|
+
|
|
102
|
+
Dependência nova: `jose` (Vercel/Cloudflare-friendly, sem Node nativo).
|
|
103
|
+
|
|
104
|
+
- **`MockAuth.verifyToken(token)`** — decodifica JWT sem verificar assinatura (útil em testes).
|
|
105
|
+
Aceita `__mockVerifyToken(fn)` pra injetar falhas determinísticas.
|
|
106
|
+
|
|
107
|
+
- **`VerifyTokenOptions`** — exportado em `index.ts` pra consumers tiparem as options.
|
|
108
|
+
|
|
109
|
+
### Usage
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
// Next.js route handler
|
|
113
|
+
import { createNeetruClient } from '@neetru/sdk';
|
|
114
|
+
const client = createNeetruClient({ apiKey: 'nrt_a1b2c3_...', env: 'prod' });
|
|
115
|
+
|
|
116
|
+
export async function POST(req: Request) {
|
|
117
|
+
const token = req.cookies.get('neetru-session')?.value ?? '';
|
|
118
|
+
try {
|
|
119
|
+
const user = await client.auth.verifyToken(token);
|
|
120
|
+
// user.uid, user.email, user.isStaff, user.tenantId disponíveis
|
|
121
|
+
} catch (err) {
|
|
122
|
+
return new Response('Unauthorized', { status: 401 });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
8
127
|
## [Unreleased]
|
|
9
128
|
|
|
10
|
-
### Planned (post-2.
|
|
129
|
+
### Planned (post-2.2)
|
|
11
130
|
- size-limit budget no CI (<12KB gz core com db namespace).
|
|
12
131
|
- CDN distribution (`cdn.neetru.com/sdk/v2/`).
|
|
13
132
|
- Endpoint batch `/api/v1/sdk/telemetry/batch` (substitui drenagem N×1 do `track()` por 1 request).
|
package/dist/auth.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var jose = require('jose');
|
|
3
4
|
var idb = require('idb');
|
|
4
5
|
|
|
5
6
|
var __defProp = Object.defineProperty;
|
|
@@ -1162,6 +1163,125 @@ var LocalStore = class {
|
|
|
1162
1163
|
}
|
|
1163
1164
|
};
|
|
1164
1165
|
|
|
1166
|
+
// src/db/offline/memory-store.ts
|
|
1167
|
+
var MemoryStore = class {
|
|
1168
|
+
// store `documents` — key: `${collection}\0${id}`
|
|
1169
|
+
_docs = /* @__PURE__ */ new Map();
|
|
1170
|
+
// store `mutations` — key: mutationId
|
|
1171
|
+
_mutations = /* @__PURE__ */ new Map();
|
|
1172
|
+
// store `sync_meta` — key: string
|
|
1173
|
+
_meta = /* @__PURE__ */ new Map();
|
|
1174
|
+
// store `conflict_log` — autoIncrement int key
|
|
1175
|
+
_conflicts = [];
|
|
1176
|
+
_conflictSeq = 0;
|
|
1177
|
+
// store `query_cache` — key: queryHash
|
|
1178
|
+
_queryCache = /* @__PURE__ */ new Map();
|
|
1179
|
+
// ─── Ciclo de vida (no-ops) ─────────────────────────────────────────────────
|
|
1180
|
+
async open() {
|
|
1181
|
+
}
|
|
1182
|
+
async close() {
|
|
1183
|
+
this._docs.clear();
|
|
1184
|
+
this._mutations.clear();
|
|
1185
|
+
this._meta.clear();
|
|
1186
|
+
this._conflicts.splice(0);
|
|
1187
|
+
this._conflictSeq = 0;
|
|
1188
|
+
this._queryCache.clear();
|
|
1189
|
+
}
|
|
1190
|
+
// ─── Documents ─────────────────────────────────────────────────────────────
|
|
1191
|
+
async getDoc(collection, id) {
|
|
1192
|
+
const key = `${collection}\0${id}`;
|
|
1193
|
+
return this._docs.get(key) ?? null;
|
|
1194
|
+
}
|
|
1195
|
+
async putDoc(doc) {
|
|
1196
|
+
const key = `${doc.collection}\0${doc.id}`;
|
|
1197
|
+
this._docs.set(key, doc);
|
|
1198
|
+
}
|
|
1199
|
+
async deleteDoc(collection, id) {
|
|
1200
|
+
const key = `${collection}\0${id}`;
|
|
1201
|
+
const existing = this._docs.get(key);
|
|
1202
|
+
if (!existing) return;
|
|
1203
|
+
this._docs.set(key, {
|
|
1204
|
+
...existing,
|
|
1205
|
+
meta: {
|
|
1206
|
+
...existing.meta,
|
|
1207
|
+
deleted: true,
|
|
1208
|
+
updatedAtLocal: Date.now()
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
async listDocs(collection, query) {
|
|
1213
|
+
const rawDocs = Array.from(this._docs.values()).filter(
|
|
1214
|
+
(d) => d.collection === collection
|
|
1215
|
+
);
|
|
1216
|
+
return QueryEngine.evaluate(rawDocs, query);
|
|
1217
|
+
}
|
|
1218
|
+
// ─── Sync meta ──────────────────────────────────────────────────────────────
|
|
1219
|
+
async getMeta(key) {
|
|
1220
|
+
return this._meta.get(key) ?? null;
|
|
1221
|
+
}
|
|
1222
|
+
async setMeta(key, value) {
|
|
1223
|
+
this._meta.set(key, value);
|
|
1224
|
+
}
|
|
1225
|
+
// ─── Conflict log ────────────────────────────────────────────────────────────
|
|
1226
|
+
async appendConflict(record) {
|
|
1227
|
+
const id = ++this._conflictSeq;
|
|
1228
|
+
const full = { ...record, id };
|
|
1229
|
+
this._conflicts.push(full);
|
|
1230
|
+
return full;
|
|
1231
|
+
}
|
|
1232
|
+
async listConflicts(options) {
|
|
1233
|
+
if (options?.delivered !== void 0) {
|
|
1234
|
+
return this._conflicts.filter((r) => r.delivered === options.delivered);
|
|
1235
|
+
}
|
|
1236
|
+
return [...this._conflicts];
|
|
1237
|
+
}
|
|
1238
|
+
async markConflictDelivered(id) {
|
|
1239
|
+
const entry = this._conflicts.find((r) => r.id === id);
|
|
1240
|
+
if (entry) entry.delivered = true;
|
|
1241
|
+
}
|
|
1242
|
+
// ─── Mutations ───────────────────────────────────────────────────────────────
|
|
1243
|
+
async putMutation(mutation) {
|
|
1244
|
+
this._mutations.set(mutation.mutationId, { ...mutation });
|
|
1245
|
+
}
|
|
1246
|
+
async getMutation(mutationId) {
|
|
1247
|
+
return this._mutations.get(mutationId) ?? null;
|
|
1248
|
+
}
|
|
1249
|
+
async listMutations(options) {
|
|
1250
|
+
let all = Array.from(this._mutations.values());
|
|
1251
|
+
if (options?.status !== void 0) {
|
|
1252
|
+
all = all.filter((m) => m.status === options.status);
|
|
1253
|
+
} else if (options?.collection !== void 0 && options.docId !== void 0) {
|
|
1254
|
+
all = all.filter(
|
|
1255
|
+
(m) => m.collection === options.collection && m.docId === options.docId
|
|
1256
|
+
);
|
|
1257
|
+
} else if (options?.collection !== void 0) {
|
|
1258
|
+
all = all.filter((m) => m.collection === options.collection);
|
|
1259
|
+
}
|
|
1260
|
+
return all.sort((a, b) => a.seq - b.seq);
|
|
1261
|
+
}
|
|
1262
|
+
async deleteMutation(mutationId) {
|
|
1263
|
+
this._mutations.delete(mutationId);
|
|
1264
|
+
}
|
|
1265
|
+
// ─── Collection discovery ────────────────────────────────────────────────────
|
|
1266
|
+
async listCollections() {
|
|
1267
|
+
const collections = /* @__PURE__ */ new Set();
|
|
1268
|
+
for (const doc of this._docs.values()) {
|
|
1269
|
+
collections.add(doc.collection);
|
|
1270
|
+
}
|
|
1271
|
+
return Array.from(collections);
|
|
1272
|
+
}
|
|
1273
|
+
// ─── Query cache ─────────────────────────────────────────────────────────────
|
|
1274
|
+
async getQueryCache(queryHash) {
|
|
1275
|
+
return this._queryCache.get(queryHash) ?? null;
|
|
1276
|
+
}
|
|
1277
|
+
async putQueryCache(entry) {
|
|
1278
|
+
this._queryCache.set(entry.queryHash, { ...entry });
|
|
1279
|
+
}
|
|
1280
|
+
async deleteQueryCache(queryHash) {
|
|
1281
|
+
this._queryCache.delete(queryHash);
|
|
1282
|
+
}
|
|
1283
|
+
};
|
|
1284
|
+
|
|
1165
1285
|
// src/db/offline/mutation-queue.ts
|
|
1166
1286
|
function generateUUIDv4() {
|
|
1167
1287
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
@@ -2708,6 +2828,9 @@ var TabCoordinator = class {
|
|
|
2708
2828
|
};
|
|
2709
2829
|
|
|
2710
2830
|
// src/db/collection-ref.ts
|
|
2831
|
+
function isIndexedDBUnavailable() {
|
|
2832
|
+
return typeof indexedDB === "undefined" || typeof window === "undefined";
|
|
2833
|
+
}
|
|
2711
2834
|
var COLL_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/;
|
|
2712
2835
|
function assertValidCollection(name) {
|
|
2713
2836
|
if (!COLL_RE.test(name)) {
|
|
@@ -3249,7 +3372,7 @@ var NeetruDbDocumentsImpl = class {
|
|
|
3249
3372
|
}
|
|
3250
3373
|
};
|
|
3251
3374
|
async function createOfflineDocumentsNamespace(opts) {
|
|
3252
|
-
const store = new LocalStore(opts.dbName);
|
|
3375
|
+
const store = isIndexedDBUnavailable() ? new MemoryStore() : new LocalStore(opts.dbName);
|
|
3253
3376
|
await store.open();
|
|
3254
3377
|
const queue = new MutationQueue(store);
|
|
3255
3378
|
const resolver = new ConflictResolver();
|
|
@@ -4019,16 +4142,16 @@ var defaultWebSocketFactory = (url) => {
|
|
|
4019
4142
|
// src/db/client-db.ts
|
|
4020
4143
|
var DATASTORE_COLLECTION_ENDPOINT = (collection) => `/api/sdk/v1/datastore/${encodeURIComponent(collection)}`;
|
|
4021
4144
|
var DATASTORE_DOC_ENDPOINT = (collection, docId) => `/api/sdk/v1/datastore/${encodeURIComponent(collection)}/${encodeURIComponent(docId)}`;
|
|
4022
|
-
function resolveTransport(
|
|
4145
|
+
function resolveTransport(transport, opts, config) {
|
|
4023
4146
|
if (opts._transport) {
|
|
4024
4147
|
return opts._transport;
|
|
4025
4148
|
}
|
|
4026
|
-
if (
|
|
4149
|
+
if (transport === "firestore") {
|
|
4027
4150
|
if (opts.firestoreTransport) {
|
|
4028
4151
|
return opts.firestoreTransport;
|
|
4029
4152
|
}
|
|
4030
4153
|
}
|
|
4031
|
-
if (
|
|
4154
|
+
if (transport === "nosql-vm" && opts.realtimeGatewayUrl) {
|
|
4032
4155
|
return createWebSocketSyncTransport(opts.realtimeGatewayUrl, config, opts);
|
|
4033
4156
|
}
|
|
4034
4157
|
return createRestSyncTransport(config);
|
|
@@ -4172,8 +4295,18 @@ function buildTicketProvider(config, opts) {
|
|
|
4172
4295
|
};
|
|
4173
4296
|
}
|
|
4174
4297
|
function createNeetruDb(config, dbOpts = {}) {
|
|
4175
|
-
|
|
4176
|
-
|
|
4298
|
+
let resolvedTransport;
|
|
4299
|
+
if (dbOpts.transport !== void 0) {
|
|
4300
|
+
resolvedTransport = dbOpts.transport;
|
|
4301
|
+
} else if (dbOpts.engine !== void 0) {
|
|
4302
|
+
console.warn(
|
|
4303
|
+
'[neetru/sdk] NeetruDbOptions.engine est\xE1 depreciado e ser\xE1 removido em v3.0. Use NeetruDbOptions.transport em vez de engine. Valores v\xE1lidos: "rest" | "firestore" | "nosql-vm". Nota: "engine" no SDK configura o TRANSPORTE (REST/Firestore/WebSocket), n\xE3o o storage engine do admin database (firestore-instance, cloud-sql-postgres, \u2026).'
|
|
4304
|
+
);
|
|
4305
|
+
resolvedTransport = dbOpts.engine;
|
|
4306
|
+
} else {
|
|
4307
|
+
resolvedTransport = "rest";
|
|
4308
|
+
}
|
|
4309
|
+
const transport = resolveTransport(resolvedTransport, dbOpts, config);
|
|
4177
4310
|
const dbId = dbOpts.dbId ?? "default";
|
|
4178
4311
|
const dbName = dbOpts.dbName ?? `neetru-db__${config.productId ?? "sdk"}__${dbId}__${config.env}`;
|
|
4179
4312
|
let _docsPromise = null;
|
|
@@ -4845,6 +4978,61 @@ var MockAuth = class {
|
|
|
4845
4978
|
this._listeners.delete(listener);
|
|
4846
4979
|
};
|
|
4847
4980
|
}
|
|
4981
|
+
/**
|
|
4982
|
+
* Mock de `verifyToken` — decodifica o JWT sem verificar assinatura (pra testes).
|
|
4983
|
+
* Em dev, retorna o user atual do mock se já estiver logado. Aceita qualquer token
|
|
4984
|
+
* não-vazio sem chamar rede.
|
|
4985
|
+
*
|
|
4986
|
+
* Para simular falhas em testes, use `MockAuth.__mockVerifyToken(fn)`.
|
|
4987
|
+
*/
|
|
4988
|
+
async verifyToken(token) {
|
|
4989
|
+
if (this._verifyTokenOverride) {
|
|
4990
|
+
return this._verifyTokenOverride(token);
|
|
4991
|
+
}
|
|
4992
|
+
if (!token) {
|
|
4993
|
+
throw Object.assign(new Error("Token inv\xE1lido"), { code: "token_invalid_signature" });
|
|
4994
|
+
}
|
|
4995
|
+
try {
|
|
4996
|
+
const parts = token.split(".");
|
|
4997
|
+
if (parts.length === 3) {
|
|
4998
|
+
const b64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
4999
|
+
const pad = "=".repeat((4 - b64.length % 4) % 4);
|
|
5000
|
+
const decoded = JSON.parse(
|
|
5001
|
+
typeof atob === "function" ? atob(b64 + pad) : Buffer.from(b64 + pad, "base64").toString("utf-8")
|
|
5002
|
+
);
|
|
5003
|
+
if (typeof decoded.sub === "string") {
|
|
5004
|
+
return {
|
|
5005
|
+
uid: decoded.sub,
|
|
5006
|
+
email: typeof decoded.email === "string" ? decoded.email : "",
|
|
5007
|
+
emailVerified: typeof decoded.email_verified === "boolean" ? decoded.email_verified : void 0,
|
|
5008
|
+
displayName: typeof decoded.name === "string" ? decoded.name : void 0,
|
|
5009
|
+
photoURL: typeof decoded.picture === "string" ? decoded.picture : void 0,
|
|
5010
|
+
isStaff: decoded.is_staff === true,
|
|
5011
|
+
isCustomer: decoded.is_customer === true,
|
|
5012
|
+
tenantId: typeof decoded.tenant_id === "string" ? decoded.tenant_id : void 0,
|
|
5013
|
+
...decoded
|
|
5014
|
+
};
|
|
5015
|
+
}
|
|
5016
|
+
}
|
|
5017
|
+
} catch {
|
|
5018
|
+
}
|
|
5019
|
+
if (this._user) return this._user;
|
|
5020
|
+
throw Object.assign(new Error("Nenhum user mock dispon\xEDvel"), { code: "token_invalid_signature" });
|
|
5021
|
+
}
|
|
5022
|
+
_verifyTokenOverride = null;
|
|
5023
|
+
/**
|
|
5024
|
+
* Injeta override pra `verifyToken` em testes — permite simular expiração,
|
|
5025
|
+
* assinatura inválida etc sem precisar de JWT real.
|
|
5026
|
+
*
|
|
5027
|
+
* @example
|
|
5028
|
+
* ```ts
|
|
5029
|
+
* const mock = new MockAuth(null);
|
|
5030
|
+
* mock.__mockVerifyToken(async () => { throw new NeetruError('token_expired', '...'); });
|
|
5031
|
+
* ```
|
|
5032
|
+
*/
|
|
5033
|
+
__mockVerifyToken(fn) {
|
|
5034
|
+
this._verifyTokenOverride = fn;
|
|
5035
|
+
}
|
|
4848
5036
|
/** Helper de tests — força um user state arbitrário. */
|
|
4849
5037
|
__setUser(user) {
|
|
4850
5038
|
this._user = user;
|
|
@@ -5018,6 +5206,37 @@ function resolveEnv(configEnv) {
|
|
|
5018
5206
|
}
|
|
5019
5207
|
return "prod";
|
|
5020
5208
|
}
|
|
5209
|
+
var _jwksCache = /* @__PURE__ */ new Map();
|
|
5210
|
+
var DEFAULT_JWKS_TTL_MS = 60 * 60 * 1e3;
|
|
5211
|
+
function _getJwks(jwksUrl, cacheTtlMs) {
|
|
5212
|
+
const now = Date.now();
|
|
5213
|
+
const cached = _jwksCache.get(jwksUrl);
|
|
5214
|
+
if (cached && now - cached.createdAt < cacheTtlMs) {
|
|
5215
|
+
return cached.jwks;
|
|
5216
|
+
}
|
|
5217
|
+
const jwks = jose.createRemoteJWKSet(new URL(jwksUrl));
|
|
5218
|
+
_jwksCache.set(jwksUrl, { jwks, createdAt: now });
|
|
5219
|
+
return jwks;
|
|
5220
|
+
}
|
|
5221
|
+
function __resetJwksCacheForTests() {
|
|
5222
|
+
_jwksCache.clear();
|
|
5223
|
+
}
|
|
5224
|
+
function _jwtPayloadToUser(payload) {
|
|
5225
|
+
const sub = payload.sub ?? "";
|
|
5226
|
+
const email = typeof payload.email === "string" ? payload.email : "";
|
|
5227
|
+
return {
|
|
5228
|
+
uid: sub,
|
|
5229
|
+
email,
|
|
5230
|
+
emailVerified: typeof payload.email_verified === "boolean" ? payload.email_verified : void 0,
|
|
5231
|
+
displayName: typeof payload.name === "string" ? payload.name : void 0,
|
|
5232
|
+
photoURL: typeof payload.picture === "string" ? payload.picture : void 0,
|
|
5233
|
+
isStaff: payload.is_staff === true,
|
|
5234
|
+
isCustomer: payload.is_customer === true,
|
|
5235
|
+
tenantId: typeof payload.tenant_id === "string" ? payload.tenant_id : void 0,
|
|
5236
|
+
// Spreads todos os claims originais (aud, iss, iat, exp, etc).
|
|
5237
|
+
...payload
|
|
5238
|
+
};
|
|
5239
|
+
}
|
|
5021
5240
|
function createOidcAuthNamespace(config) {
|
|
5022
5241
|
const STORAGE_KEY = "neetru_id_token";
|
|
5023
5242
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -5125,6 +5344,45 @@ function createOidcAuthNamespace(config) {
|
|
|
5125
5344
|
return () => {
|
|
5126
5345
|
listeners.delete(listener);
|
|
5127
5346
|
};
|
|
5347
|
+
},
|
|
5348
|
+
async verifyToken(token, options) {
|
|
5349
|
+
if (!token || typeof token !== "string") {
|
|
5350
|
+
throw new NeetruError("token_invalid_signature", "token must be a non-empty string");
|
|
5351
|
+
}
|
|
5352
|
+
const overrideAuthUrl = typeof globalThis.NEETRU_AUTH_URL === "string" ? globalThis.NEETRU_AUTH_URL : null;
|
|
5353
|
+
const idpOrigin = overrideAuthUrl ?? config.baseUrl.replace(/^https?:\/\/api\./, "https://auth.");
|
|
5354
|
+
const issuer = idpOrigin;
|
|
5355
|
+
const jwksUrl = `${idpOrigin}/api/v1/oauth/.well-known/jwks.json`;
|
|
5356
|
+
const cacheTtlMs = options?.jwksCacheTtlMs ?? DEFAULT_JWKS_TTL_MS;
|
|
5357
|
+
const audience = config.apiKey ? config.apiKey.split("_")[1] ?? void 0 : void 0;
|
|
5358
|
+
const jwks = _getJwks(jwksUrl, cacheTtlMs);
|
|
5359
|
+
try {
|
|
5360
|
+
const { payload } = await jose.jwtVerify(token, jwks, {
|
|
5361
|
+
issuer,
|
|
5362
|
+
...audience ? { audience } : {},
|
|
5363
|
+
algorithms: ["RS256"]
|
|
5364
|
+
});
|
|
5365
|
+
return _jwtPayloadToUser(payload);
|
|
5366
|
+
} catch (err) {
|
|
5367
|
+
const errName = err?.code ?? err?.name ?? "";
|
|
5368
|
+
const errMsg = err?.message ?? String(err);
|
|
5369
|
+
if (errName === "ERR_JWT_EXPIRED") {
|
|
5370
|
+
throw new NeetruError("token_expired", `Token expired: ${errMsg}`);
|
|
5371
|
+
}
|
|
5372
|
+
if (errName === "ERR_JWS_SIGNATURE_VERIFICATION_FAILED") {
|
|
5373
|
+
throw new NeetruError("token_invalid_signature", `Token signature invalid: ${errMsg}`);
|
|
5374
|
+
}
|
|
5375
|
+
if (errName === "ERR_JWT_CLAIM_VALIDATION_FAILED") {
|
|
5376
|
+
if (errMsg.includes('"aud"') || errMsg.includes("audience")) {
|
|
5377
|
+
throw new NeetruError("token_invalid_audience", `Token audience mismatch: ${errMsg}`);
|
|
5378
|
+
}
|
|
5379
|
+
if (errMsg.includes('"iss"') || errMsg.includes("issuer")) {
|
|
5380
|
+
throw new NeetruError("token_invalid_issuer", `Token issuer mismatch: ${errMsg}`);
|
|
5381
|
+
}
|
|
5382
|
+
throw new NeetruError("token_invalid_signature", `Token claim validation failed: ${errMsg}`);
|
|
5383
|
+
}
|
|
5384
|
+
throw new NeetruError("token_invalid_signature", `Token verification failed: ${errMsg}`);
|
|
5385
|
+
}
|
|
5128
5386
|
}
|
|
5129
5387
|
};
|
|
5130
5388
|
}
|
|
@@ -5171,6 +5429,7 @@ function createNeetruClient(config = {}) {
|
|
|
5171
5429
|
return client;
|
|
5172
5430
|
}
|
|
5173
5431
|
|
|
5432
|
+
exports.__resetJwksCacheForTests = __resetJwksCacheForTests;
|
|
5174
5433
|
exports.createNeetruClient = createNeetruClient;
|
|
5175
5434
|
//# sourceMappingURL=auth.cjs.map
|
|
5176
5435
|
//# sourceMappingURL=auth.cjs.map
|