@tenxyte/core 0.9.0 → 0.9.3
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/LICENSE +21 -0
- package/README.md +362 -102
- package/dist/index.cjs +966 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1073 -15
- package/dist/index.d.ts +1073 -15
- package/dist/index.js +946 -35
- package/dist/index.js.map +1 -1
- package/package.json +15 -3
- package/patched-schema.json +0 -11388
- package/src/client.ts +0 -50
- package/src/config.ts +0 -0
- package/src/http/client.ts +0 -162
- package/src/http/index.ts +0 -1
- package/src/http/interceptors.ts +0 -117
- package/src/index.ts +0 -7
- package/src/modules/ai.ts +0 -178
- package/src/modules/auth.ts +0 -116
- package/src/modules/b2b.ts +0 -177
- package/src/modules/rbac.ts +0 -207
- package/src/modules/security.ts +0 -313
- package/src/modules/user.ts +0 -95
- package/src/storage/cookie.ts +0 -39
- package/src/storage/index.ts +0 -29
- package/src/storage/localStorage.ts +0 -75
- package/src/storage/memory.ts +0 -30
- package/src/types/api-schema.d.ts +0 -6590
- package/src/types/index.ts +0 -152
- package/src/utils/base64url.ts +0 -25
- package/src/utils/device_info.ts +0 -94
- package/src/utils/events.ts +0 -71
- package/src/utils/jwt.ts +0 -51
- package/tests/http.test.ts +0 -144
- package/tests/modules/auth.test.ts +0 -93
- package/tests/modules/rbac.test.ts +0 -95
- package/tests/modules/security.test.ts +0 -85
- package/tests/modules/user.test.ts +0 -76
- package/tests/storage.test.ts +0 -96
- package/tests/utils.test.ts +0 -71
- package/tsconfig.json +0 -26
- package/tsup.config.ts +0 -10
- package/vitest.config.ts +0 -7
package/dist/index.cjs
CHANGED
|
@@ -20,24 +20,175 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
AdminModule: () => AdminModule,
|
|
24
|
+
AiModule: () => AiModule,
|
|
25
|
+
ApplicationsModule: () => ApplicationsModule,
|
|
23
26
|
AuthModule: () => AuthModule,
|
|
27
|
+
B2bModule: () => B2bModule,
|
|
28
|
+
CookieStorage: () => CookieStorage,
|
|
29
|
+
DashboardModule: () => DashboardModule,
|
|
30
|
+
EventEmitter: () => EventEmitter,
|
|
31
|
+
GdprModule: () => GdprModule,
|
|
32
|
+
LocalStorage: () => LocalStorage,
|
|
33
|
+
MemoryStorage: () => MemoryStorage,
|
|
34
|
+
NOOP_LOGGER: () => NOOP_LOGGER,
|
|
24
35
|
RbacModule: () => RbacModule,
|
|
36
|
+
SDK_VERSION: () => SDK_VERSION,
|
|
25
37
|
SecurityModule: () => SecurityModule,
|
|
26
38
|
TenxyteClient: () => TenxyteClient,
|
|
27
39
|
TenxyteHttpClient: () => TenxyteHttpClient,
|
|
28
|
-
UserModule: () => UserModule
|
|
40
|
+
UserModule: () => UserModule,
|
|
41
|
+
buildDeviceInfo: () => buildDeviceInfo,
|
|
42
|
+
createAuthInterceptor: () => createAuthInterceptor,
|
|
43
|
+
createDeviceInfoInterceptor: () => createDeviceInfoInterceptor,
|
|
44
|
+
createRefreshInterceptor: () => createRefreshInterceptor,
|
|
45
|
+
createRetryInterceptor: () => createRetryInterceptor,
|
|
46
|
+
decodeJwt: () => decodeJwt,
|
|
47
|
+
resolveConfig: () => resolveConfig
|
|
29
48
|
});
|
|
30
49
|
module.exports = __toCommonJS(index_exports);
|
|
31
50
|
|
|
51
|
+
// src/storage/memory.ts
|
|
52
|
+
var MemoryStorage = class {
|
|
53
|
+
store;
|
|
54
|
+
constructor() {
|
|
55
|
+
this.store = /* @__PURE__ */ new Map();
|
|
56
|
+
}
|
|
57
|
+
getItem(key) {
|
|
58
|
+
const value = this.store.get(key);
|
|
59
|
+
return value !== void 0 ? value : null;
|
|
60
|
+
}
|
|
61
|
+
setItem(key, value) {
|
|
62
|
+
this.store.set(key, value);
|
|
63
|
+
}
|
|
64
|
+
removeItem(key) {
|
|
65
|
+
this.store.delete(key);
|
|
66
|
+
}
|
|
67
|
+
clear() {
|
|
68
|
+
this.store.clear();
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// src/storage/localStorage.ts
|
|
73
|
+
var LocalStorage = class {
|
|
74
|
+
fallbackMemoryStore = null;
|
|
75
|
+
isAvailable;
|
|
76
|
+
constructor() {
|
|
77
|
+
this.isAvailable = this.checkAvailability();
|
|
78
|
+
if (!this.isAvailable) {
|
|
79
|
+
this.fallbackMemoryStore = new MemoryStorage();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
checkAvailability() {
|
|
83
|
+
try {
|
|
84
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
const testKey = "__tenxyte_test__";
|
|
88
|
+
window.localStorage.setItem(testKey, "1");
|
|
89
|
+
window.localStorage.removeItem(testKey);
|
|
90
|
+
return true;
|
|
91
|
+
} catch (_e) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
getItem(key) {
|
|
96
|
+
if (!this.isAvailable && this.fallbackMemoryStore) {
|
|
97
|
+
return this.fallbackMemoryStore.getItem(key);
|
|
98
|
+
}
|
|
99
|
+
return window.localStorage.getItem(key);
|
|
100
|
+
}
|
|
101
|
+
setItem(key, value) {
|
|
102
|
+
if (!this.isAvailable && this.fallbackMemoryStore) {
|
|
103
|
+
this.fallbackMemoryStore.setItem(key, value);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
window.localStorage.setItem(key, value);
|
|
108
|
+
} catch (_e) {
|
|
109
|
+
console.warn(`[Tenxyte SDK] Warning: failed to write to localStorage for key ${key}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
removeItem(key) {
|
|
113
|
+
if (!this.isAvailable && this.fallbackMemoryStore) {
|
|
114
|
+
this.fallbackMemoryStore.removeItem(key);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
window.localStorage.removeItem(key);
|
|
118
|
+
}
|
|
119
|
+
clear() {
|
|
120
|
+
if (!this.isAvailable && this.fallbackMemoryStore) {
|
|
121
|
+
this.fallbackMemoryStore.clear();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
window.localStorage.clear();
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// src/storage/cookie.ts
|
|
129
|
+
var CookieStorage = class {
|
|
130
|
+
defaultOptions;
|
|
131
|
+
constructor(options = {}) {
|
|
132
|
+
const secure = options.secure ?? true;
|
|
133
|
+
const sameSite = options.sameSite ?? "Lax";
|
|
134
|
+
this.defaultOptions = `path=/; SameSite=${sameSite}${secure ? "; Secure" : ""}`;
|
|
135
|
+
}
|
|
136
|
+
getItem(key) {
|
|
137
|
+
if (typeof document === "undefined") return null;
|
|
138
|
+
const match = document.cookie.match(new RegExp(`(^| )${key}=([^;]+)`));
|
|
139
|
+
return match ? decodeURIComponent(match[2]) : null;
|
|
140
|
+
}
|
|
141
|
+
setItem(key, value) {
|
|
142
|
+
if (typeof document === "undefined") return;
|
|
143
|
+
document.cookie = `${key}=${encodeURIComponent(value)}; ${this.defaultOptions}`;
|
|
144
|
+
}
|
|
145
|
+
removeItem(key) {
|
|
146
|
+
if (typeof document === "undefined") return;
|
|
147
|
+
document.cookie = `${key}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
|
|
148
|
+
}
|
|
149
|
+
clear() {
|
|
150
|
+
this.removeItem("tx_access");
|
|
151
|
+
this.removeItem("tx_refresh");
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// src/config.ts
|
|
156
|
+
var SDK_VERSION = "0.9.0";
|
|
157
|
+
var NOOP_LOGGER = {
|
|
158
|
+
debug() {
|
|
159
|
+
},
|
|
160
|
+
warn() {
|
|
161
|
+
},
|
|
162
|
+
error() {
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
function resolveConfig(config) {
|
|
166
|
+
return {
|
|
167
|
+
baseUrl: config.baseUrl,
|
|
168
|
+
headers: config.headers ?? {},
|
|
169
|
+
storage: config.storage ?? new MemoryStorage(),
|
|
170
|
+
autoRefresh: config.autoRefresh ?? true,
|
|
171
|
+
autoDeviceInfo: config.autoDeviceInfo ?? true,
|
|
172
|
+
timeoutMs: config.timeoutMs,
|
|
173
|
+
onSessionExpired: config.onSessionExpired,
|
|
174
|
+
logger: config.logger ?? NOOP_LOGGER,
|
|
175
|
+
logLevel: config.logLevel ?? "silent",
|
|
176
|
+
deviceInfoOverride: config.deviceInfoOverride,
|
|
177
|
+
retryConfig: config.retryConfig
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
32
181
|
// src/http/client.ts
|
|
33
182
|
var TenxyteHttpClient = class {
|
|
34
183
|
baseUrl;
|
|
35
184
|
defaultHeaders;
|
|
185
|
+
timeoutMs;
|
|
36
186
|
// Interceptors
|
|
37
187
|
requestInterceptors = [];
|
|
38
188
|
responseInterceptors = [];
|
|
39
189
|
constructor(options) {
|
|
40
190
|
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
191
|
+
this.timeoutMs = options.timeoutMs;
|
|
41
192
|
this.defaultHeaders = {
|
|
42
193
|
"Content-Type": "application/json",
|
|
43
194
|
Accept: "application/json",
|
|
@@ -56,7 +207,7 @@ var TenxyteHttpClient = class {
|
|
|
56
207
|
*/
|
|
57
208
|
async request(endpoint, config = {}) {
|
|
58
209
|
const urlStr = endpoint.startsWith("http") ? endpoint : `${this.baseUrl}${endpoint.startsWith("/") ? "" : "/"}${endpoint}`;
|
|
59
|
-
|
|
210
|
+
const urlObj = new URL(urlStr);
|
|
60
211
|
if (config.params) {
|
|
61
212
|
Object.entries(config.params).forEach(([key, value]) => {
|
|
62
213
|
if (value !== void 0 && value !== null) {
|
|
@@ -83,6 +234,13 @@ var TenxyteHttpClient = class {
|
|
|
83
234
|
requestContext = await interceptor(requestContext);
|
|
84
235
|
}
|
|
85
236
|
const { url, ...fetchConfig } = requestContext;
|
|
237
|
+
let controller;
|
|
238
|
+
let timeoutId;
|
|
239
|
+
if (this.timeoutMs) {
|
|
240
|
+
controller = new AbortController();
|
|
241
|
+
fetchConfig.signal = controller.signal;
|
|
242
|
+
timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
243
|
+
}
|
|
86
244
|
try {
|
|
87
245
|
let response = await fetch(url, fetchConfig);
|
|
88
246
|
for (const interceptor of this.responseInterceptors) {
|
|
@@ -100,6 +258,13 @@ var TenxyteHttpClient = class {
|
|
|
100
258
|
}
|
|
101
259
|
return await response.text();
|
|
102
260
|
} catch (error) {
|
|
261
|
+
if (error?.name === "AbortError") {
|
|
262
|
+
throw {
|
|
263
|
+
error: `Request timed out after ${this.timeoutMs}ms`,
|
|
264
|
+
code: "TIMEOUT",
|
|
265
|
+
details: url
|
|
266
|
+
};
|
|
267
|
+
}
|
|
103
268
|
if (error && error.code) {
|
|
104
269
|
throw error;
|
|
105
270
|
}
|
|
@@ -108,6 +273,10 @@ var TenxyteHttpClient = class {
|
|
|
108
273
|
code: "NETWORK_ERROR",
|
|
109
274
|
details: String(error)
|
|
110
275
|
};
|
|
276
|
+
} finally {
|
|
277
|
+
if (timeoutId !== void 0) {
|
|
278
|
+
clearTimeout(timeoutId);
|
|
279
|
+
}
|
|
111
280
|
}
|
|
112
281
|
}
|
|
113
282
|
async normalizeError(response) {
|
|
@@ -119,7 +288,7 @@ var TenxyteHttpClient = class {
|
|
|
119
288
|
details: body.details || body,
|
|
120
289
|
retry_after: response.headers.has("Retry-After") ? parseInt(response.headers.get("Retry-After"), 10) : void 0
|
|
121
290
|
};
|
|
122
|
-
} catch (
|
|
291
|
+
} catch (_e) {
|
|
123
292
|
return {
|
|
124
293
|
error: `HTTP Error ${response.status}: ${response.statusText}`,
|
|
125
294
|
code: `HTTP_${response.status}`
|
|
@@ -139,15 +308,248 @@ var TenxyteHttpClient = class {
|
|
|
139
308
|
patch(endpoint, data, config) {
|
|
140
309
|
return this.request(endpoint, { ...config, method: "PATCH", body: data });
|
|
141
310
|
}
|
|
142
|
-
delete(endpoint, config) {
|
|
143
|
-
return this.request(endpoint, { ...config, method: "DELETE" });
|
|
311
|
+
delete(endpoint, data, config) {
|
|
312
|
+
return this.request(endpoint, { ...config, method: "DELETE", body: data });
|
|
144
313
|
}
|
|
145
314
|
};
|
|
146
315
|
|
|
316
|
+
// src/utils/device_info.ts
|
|
317
|
+
function buildDeviceInfo(customInfo = {}) {
|
|
318
|
+
const autoInfo = getAutoInfo();
|
|
319
|
+
const v = "1";
|
|
320
|
+
const os = customInfo.os || autoInfo.os;
|
|
321
|
+
const osv = customInfo.osVersion || autoInfo.osVersion;
|
|
322
|
+
const device = customInfo.device || autoInfo.device;
|
|
323
|
+
const arch = customInfo.arch || autoInfo.arch;
|
|
324
|
+
const app = customInfo.app || autoInfo.app;
|
|
325
|
+
const appv = customInfo.appVersion || autoInfo.appVersion;
|
|
326
|
+
const runtime = customInfo.runtime || autoInfo.runtime;
|
|
327
|
+
const rtv = customInfo.runtimeVersion || autoInfo.runtimeVersion;
|
|
328
|
+
const tz = customInfo.timezone || autoInfo.timezone;
|
|
329
|
+
const parts = [
|
|
330
|
+
`v=${v}`,
|
|
331
|
+
`os=${os}` + (osv ? `;osv=${osv}` : ""),
|
|
332
|
+
`device=${device}`,
|
|
333
|
+
arch ? `arch=${arch}` : "",
|
|
334
|
+
app ? `app=${app}${appv ? `;appv=${appv}` : ""}` : "",
|
|
335
|
+
`runtime=${runtime}` + (rtv ? `;rtv=${rtv}` : ""),
|
|
336
|
+
tz ? `tz=${tz}` : ""
|
|
337
|
+
];
|
|
338
|
+
return parts.filter(Boolean).join("|");
|
|
339
|
+
}
|
|
340
|
+
function getAutoInfo() {
|
|
341
|
+
const info = {
|
|
342
|
+
os: "unknown",
|
|
343
|
+
osVersion: "",
|
|
344
|
+
device: "desktop",
|
|
345
|
+
// default
|
|
346
|
+
arch: "",
|
|
347
|
+
app: "sdk",
|
|
348
|
+
appVersion: "0.1.0",
|
|
349
|
+
runtime: "unknown",
|
|
350
|
+
runtimeVersion: "",
|
|
351
|
+
timezone: ""
|
|
352
|
+
};
|
|
353
|
+
try {
|
|
354
|
+
if (typeof Intl !== "undefined") {
|
|
355
|
+
info.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
356
|
+
}
|
|
357
|
+
if (typeof process !== "undefined" && process.version) {
|
|
358
|
+
info.runtime = "node";
|
|
359
|
+
info.runtimeVersion = process.version;
|
|
360
|
+
info.os = process.platform;
|
|
361
|
+
info.arch = process.arch;
|
|
362
|
+
info.device = "server";
|
|
363
|
+
} else if (typeof window !== "undefined" && window.navigator) {
|
|
364
|
+
const ua = window.navigator.userAgent.toLowerCase();
|
|
365
|
+
if (ua.includes("windows")) info.os = "windows";
|
|
366
|
+
else if (ua.includes("mac")) info.os = "macos";
|
|
367
|
+
else if (ua.includes("linux")) info.os = "linux";
|
|
368
|
+
else if (ua.includes("android")) info.os = "android";
|
|
369
|
+
else if (ua.includes("ios") || ua.includes("iphone") || ua.includes("ipad")) info.os = "ios";
|
|
370
|
+
if (/mobi|android|touch|mini/i.test(ua)) info.device = "mobile";
|
|
371
|
+
if (/tablet|ipad/i.test(ua)) info.device = "tablet";
|
|
372
|
+
if (ua.includes("firefox")) info.runtime = "firefox";
|
|
373
|
+
else if (ua.includes("edg/")) info.runtime = "edge";
|
|
374
|
+
else if (ua.includes("chrome")) info.runtime = "chrome";
|
|
375
|
+
else if (ua.includes("safari")) info.runtime = "safari";
|
|
376
|
+
}
|
|
377
|
+
} catch (_e) {
|
|
378
|
+
}
|
|
379
|
+
return info;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// src/http/interceptors.ts
|
|
383
|
+
function createAuthInterceptor(storage, context) {
|
|
384
|
+
return async (request) => {
|
|
385
|
+
const token = await storage.getItem("tx_access");
|
|
386
|
+
const headers = { ...request.headers || {} };
|
|
387
|
+
if (token && !headers["Authorization"]) {
|
|
388
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
389
|
+
}
|
|
390
|
+
if (context.activeOrgSlug && !headers["X-Org-Slug"]) {
|
|
391
|
+
headers["X-Org-Slug"] = context.activeOrgSlug;
|
|
392
|
+
}
|
|
393
|
+
if (context.agentTraceId && !headers["X-Prompt-Trace-ID"]) {
|
|
394
|
+
headers["X-Prompt-Trace-ID"] = context.agentTraceId;
|
|
395
|
+
}
|
|
396
|
+
return { ...request, headers };
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
function createRefreshInterceptor(client, storage, onSessionExpired, onTokenRefreshed) {
|
|
400
|
+
let isRefreshing = false;
|
|
401
|
+
let refreshQueue = [];
|
|
402
|
+
const processQueue = (error, token = null) => {
|
|
403
|
+
refreshQueue.forEach((prom) => prom(token));
|
|
404
|
+
refreshQueue = [];
|
|
405
|
+
};
|
|
406
|
+
return async (response, request) => {
|
|
407
|
+
if (response.status === 401 && !request.url.includes("/auth/refresh") && !request.url.includes("/auth/login")) {
|
|
408
|
+
const refreshToken = await storage.getItem("tx_refresh");
|
|
409
|
+
if (!refreshToken) {
|
|
410
|
+
onSessionExpired();
|
|
411
|
+
return response;
|
|
412
|
+
}
|
|
413
|
+
if (isRefreshing) {
|
|
414
|
+
return new Promise((resolve) => {
|
|
415
|
+
refreshQueue.push((newToken) => {
|
|
416
|
+
if (newToken) {
|
|
417
|
+
const retryHeaders = { ...request.config.headers, Authorization: `Bearer ${newToken}` };
|
|
418
|
+
resolve(fetch(request.url, { ...request.config, headers: retryHeaders }));
|
|
419
|
+
} else {
|
|
420
|
+
resolve(response);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
isRefreshing = true;
|
|
426
|
+
try {
|
|
427
|
+
const refreshResponse = await fetch(`${client["baseUrl"]}/auth/refresh/`, {
|
|
428
|
+
method: "POST",
|
|
429
|
+
headers: { "Content-Type": "application/json" },
|
|
430
|
+
body: JSON.stringify({ refresh_token: refreshToken })
|
|
431
|
+
});
|
|
432
|
+
if (!refreshResponse.ok) {
|
|
433
|
+
throw new Error("Refresh failed");
|
|
434
|
+
}
|
|
435
|
+
const data = await refreshResponse.json();
|
|
436
|
+
await storage.setItem("tx_access", data.access);
|
|
437
|
+
if (data.refresh) {
|
|
438
|
+
await storage.setItem("tx_refresh", data.refresh);
|
|
439
|
+
}
|
|
440
|
+
isRefreshing = false;
|
|
441
|
+
onTokenRefreshed?.(data.access, data.refresh);
|
|
442
|
+
processQueue(null, data.access);
|
|
443
|
+
const retryHeaders = { ...request.config.headers, Authorization: `Bearer ${data.access}` };
|
|
444
|
+
const r = await fetch(request.url, { ...request.config, headers: retryHeaders });
|
|
445
|
+
return r;
|
|
446
|
+
} catch (err) {
|
|
447
|
+
isRefreshing = false;
|
|
448
|
+
await storage.removeItem("tx_access");
|
|
449
|
+
await storage.removeItem("tx_refresh");
|
|
450
|
+
processQueue(err, null);
|
|
451
|
+
onSessionExpired();
|
|
452
|
+
return response;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return response;
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
var DEVICE_INFO_ENDPOINTS = [
|
|
459
|
+
"/login/email/",
|
|
460
|
+
"/login/phone/",
|
|
461
|
+
"/register/",
|
|
462
|
+
"/social/"
|
|
463
|
+
];
|
|
464
|
+
var DEFAULT_RETRY = {
|
|
465
|
+
maxRetries: 3,
|
|
466
|
+
retryOn429: true,
|
|
467
|
+
retryOnNetworkError: true,
|
|
468
|
+
baseDelayMs: 1e3
|
|
469
|
+
};
|
|
470
|
+
function sleep(ms) {
|
|
471
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
472
|
+
}
|
|
473
|
+
function createRetryInterceptor(config = {}, logger) {
|
|
474
|
+
const opts = { ...DEFAULT_RETRY, ...config };
|
|
475
|
+
return async (response, request) => {
|
|
476
|
+
const shouldRetry429 = opts.retryOn429 && response.status === 429;
|
|
477
|
+
const shouldRetryServer = response.status >= 500;
|
|
478
|
+
if (!shouldRetry429 && !shouldRetryServer) {
|
|
479
|
+
return response;
|
|
480
|
+
}
|
|
481
|
+
let lastResponse = response;
|
|
482
|
+
for (let attempt = 1; attempt <= opts.maxRetries; attempt++) {
|
|
483
|
+
let delayMs = opts.baseDelayMs * Math.pow(2, attempt - 1);
|
|
484
|
+
const retryAfter = lastResponse.headers.get("Retry-After");
|
|
485
|
+
if (retryAfter) {
|
|
486
|
+
const parsed = Number(retryAfter);
|
|
487
|
+
if (!isNaN(parsed)) {
|
|
488
|
+
delayMs = parsed * 1e3;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
logger?.debug(`[Tenxyte Retry] Attempt ${attempt}/${opts.maxRetries} after ${delayMs}ms for ${request.url}`);
|
|
492
|
+
await sleep(delayMs);
|
|
493
|
+
try {
|
|
494
|
+
const retryResponse = await fetch(request.url, request.config);
|
|
495
|
+
if (retryResponse.status === 429 && opts.retryOn429 && attempt < opts.maxRetries) {
|
|
496
|
+
lastResponse = retryResponse;
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
if (retryResponse.status >= 500 && attempt < opts.maxRetries) {
|
|
500
|
+
lastResponse = retryResponse;
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
return retryResponse;
|
|
504
|
+
} catch (err) {
|
|
505
|
+
if (!opts.retryOnNetworkError || attempt >= opts.maxRetries) {
|
|
506
|
+
throw err;
|
|
507
|
+
}
|
|
508
|
+
logger?.warn(`[Tenxyte Retry] Network error on attempt ${attempt}/${opts.maxRetries}`, err);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return lastResponse;
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
function createDeviceInfoInterceptor(override) {
|
|
515
|
+
const fingerprint = buildDeviceInfo(override);
|
|
516
|
+
return (request) => {
|
|
517
|
+
const isPost = !request.method || request.method === "POST";
|
|
518
|
+
const matchesEndpoint = DEVICE_INFO_ENDPOINTS.some((ep) => request.url.includes(ep));
|
|
519
|
+
if (isPost && matchesEndpoint && request.body && typeof request.body === "object") {
|
|
520
|
+
const body = request.body;
|
|
521
|
+
if (!body.device_info) {
|
|
522
|
+
return { ...request, body: { ...body, device_info: fingerprint } };
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return request;
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
147
529
|
// src/modules/auth.ts
|
|
148
530
|
var AuthModule = class {
|
|
149
|
-
constructor(client) {
|
|
531
|
+
constructor(client, storage, onTokens, onLogout) {
|
|
150
532
|
this.client = client;
|
|
533
|
+
this.storage = storage;
|
|
534
|
+
this.onTokens = onTokens;
|
|
535
|
+
this.onLogout = onLogout;
|
|
536
|
+
}
|
|
537
|
+
async clearTokens() {
|
|
538
|
+
if (this.storage) {
|
|
539
|
+
await this.storage.removeItem("tx_access");
|
|
540
|
+
await this.storage.removeItem("tx_refresh");
|
|
541
|
+
this.onLogout?.();
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
async persistTokens(tokens) {
|
|
545
|
+
if (this.storage) {
|
|
546
|
+
await this.storage.setItem("tx_access", tokens.access_token);
|
|
547
|
+
if (tokens.refresh_token) {
|
|
548
|
+
await this.storage.setItem("tx_refresh", tokens.refresh_token);
|
|
549
|
+
}
|
|
550
|
+
this.onTokens?.(tokens.access_token, tokens.refresh_token);
|
|
551
|
+
}
|
|
552
|
+
return tokens;
|
|
151
553
|
}
|
|
152
554
|
/**
|
|
153
555
|
* Authenticate a user with their email and password.
|
|
@@ -156,7 +558,8 @@ var AuthModule = class {
|
|
|
156
558
|
* @throws {TenxyteError} If credentials are invalid, or if `2FA_REQUIRED` without a valid `totp_code`.
|
|
157
559
|
*/
|
|
158
560
|
async loginWithEmail(data) {
|
|
159
|
-
|
|
561
|
+
const tokens = await this.client.post("/api/v1/auth/login/email/", data);
|
|
562
|
+
return this.persistTokens(tokens);
|
|
160
563
|
}
|
|
161
564
|
/**
|
|
162
565
|
* Authenticate a user with an international phone number and password.
|
|
@@ -164,7 +567,8 @@ var AuthModule = class {
|
|
|
164
567
|
* @returns A pair of Access and Refresh tokens.
|
|
165
568
|
*/
|
|
166
569
|
async loginWithPhone(data) {
|
|
167
|
-
|
|
570
|
+
const tokens = await this.client.post("/api/v1/auth/login/phone/", data);
|
|
571
|
+
return this.persistTokens(tokens);
|
|
168
572
|
}
|
|
169
573
|
/**
|
|
170
574
|
* Registers a new user account.
|
|
@@ -172,7 +576,11 @@ var AuthModule = class {
|
|
|
172
576
|
* @returns The registered user data or a confirmation message.
|
|
173
577
|
*/
|
|
174
578
|
async register(data) {
|
|
175
|
-
|
|
579
|
+
const result = await this.client.post("/api/v1/auth/register/", data);
|
|
580
|
+
if (result?.access_token) {
|
|
581
|
+
await this.persistTokens(result);
|
|
582
|
+
}
|
|
583
|
+
return result;
|
|
176
584
|
}
|
|
177
585
|
/**
|
|
178
586
|
* Logout from the current session.
|
|
@@ -180,14 +588,26 @@ var AuthModule = class {
|
|
|
180
588
|
* @param refreshToken - The refresh token to revoke.
|
|
181
589
|
*/
|
|
182
590
|
async logout(refreshToken) {
|
|
183
|
-
|
|
591
|
+
await this.client.post("/api/v1/auth/logout/", { refresh_token: refreshToken });
|
|
592
|
+
await this.clearTokens();
|
|
184
593
|
}
|
|
185
594
|
/**
|
|
186
595
|
* Logout from all sessions across all devices.
|
|
187
596
|
* Revokes all refresh tokens currently assigned to the user.
|
|
188
597
|
*/
|
|
189
598
|
async logoutAll() {
|
|
190
|
-
|
|
599
|
+
await this.client.post("/api/v1/auth/logout/all/");
|
|
600
|
+
await this.clearTokens();
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Manually refresh the access token using a valid refresh token.
|
|
604
|
+
* The refresh token is automatically rotated for improved security.
|
|
605
|
+
* @param refreshToken - The current refresh token.
|
|
606
|
+
* @returns A new token pair (access + rotated refresh).
|
|
607
|
+
*/
|
|
608
|
+
async refreshToken(refreshToken) {
|
|
609
|
+
const tokens = await this.client.post("/api/v1/auth/refresh/", { refresh_token: refreshToken });
|
|
610
|
+
return this.persistTokens(tokens);
|
|
191
611
|
}
|
|
192
612
|
/**
|
|
193
613
|
* Request a Magic Link for passwordless sign-in.
|
|
@@ -202,7 +622,8 @@ var AuthModule = class {
|
|
|
202
622
|
* @returns A session token pair if the token is valid and unexpired.
|
|
203
623
|
*/
|
|
204
624
|
async verifyMagicLink(token) {
|
|
205
|
-
|
|
625
|
+
const tokens = await this.client.get(`/api/v1/auth/magic-link/verify/`, { params: { token } });
|
|
626
|
+
return this.persistTokens(tokens);
|
|
206
627
|
}
|
|
207
628
|
/**
|
|
208
629
|
* Submits OAuth2 Social Authentication payloads to the backend.
|
|
@@ -212,7 +633,8 @@ var AuthModule = class {
|
|
|
212
633
|
* @returns An active session token pair.
|
|
213
634
|
*/
|
|
214
635
|
async loginWithSocial(provider, data) {
|
|
215
|
-
|
|
636
|
+
const tokens = await this.client.post(`/api/v1/auth/social/${provider}/`, data);
|
|
637
|
+
return this.persistTokens(tokens);
|
|
216
638
|
}
|
|
217
639
|
/**
|
|
218
640
|
* Handle Social Auth Callbacks (Authorization Code flow).
|
|
@@ -222,9 +644,10 @@ var AuthModule = class {
|
|
|
222
644
|
* @returns An active session token pair after successful code exchange.
|
|
223
645
|
*/
|
|
224
646
|
async handleSocialCallback(provider, code, redirectUri) {
|
|
225
|
-
|
|
647
|
+
const tokens = await this.client.get(`/api/v1/auth/social/${provider}/callback/`, {
|
|
226
648
|
params: { code, redirect_uri: redirectUri }
|
|
227
649
|
});
|
|
650
|
+
return this.persistTokens(tokens);
|
|
228
651
|
}
|
|
229
652
|
};
|
|
230
653
|
|
|
@@ -446,7 +869,7 @@ function decodeJwt(token) {
|
|
|
446
869
|
if (parts.length !== 3) {
|
|
447
870
|
return null;
|
|
448
871
|
}
|
|
449
|
-
|
|
872
|
+
const base64Url = parts[1];
|
|
450
873
|
if (!base64Url) return null;
|
|
451
874
|
let base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
|
|
452
875
|
while (base64.length % 4) {
|
|
@@ -462,7 +885,7 @@ function decodeJwt(token) {
|
|
|
462
885
|
jsonPayload = Buffer.from(base64, "base64").toString("utf8");
|
|
463
886
|
}
|
|
464
887
|
return JSON.parse(jsonPayload);
|
|
465
|
-
} catch (
|
|
888
|
+
} catch (_e) {
|
|
466
889
|
return null;
|
|
467
890
|
}
|
|
468
891
|
}
|
|
@@ -569,12 +992,7 @@ var RbacModule = class {
|
|
|
569
992
|
return this.client.post(`/api/v1/auth/roles/${roleId}/permissions/`, { permission_codes });
|
|
570
993
|
}
|
|
571
994
|
async removePermissionsFromRole(roleId, permission_codes) {
|
|
572
|
-
return this.client.delete(`/api/v1/auth/roles/${roleId}/permissions/`, {
|
|
573
|
-
// Note: DELETE request with body is supported via our fetch wrapper if enabled,
|
|
574
|
-
// or we might need to rely on query strings. The schema specifies body or query.
|
|
575
|
-
// Let's pass it in body via a custom config or URL params.
|
|
576
|
-
body: { permission_codes }
|
|
577
|
-
});
|
|
995
|
+
return this.client.delete(`/api/v1/auth/roles/${roleId}/permissions/`, { permission_codes });
|
|
578
996
|
}
|
|
579
997
|
// --- Permissions CRUD --- //
|
|
580
998
|
/** Enumerates all available fine-grained Permissions inside this Tenant scope. */
|
|
@@ -598,6 +1016,20 @@ var RbacModule = class {
|
|
|
598
1016
|
return this.client.delete(`/api/v1/auth/permissions/${permissionId}/`);
|
|
599
1017
|
}
|
|
600
1018
|
// --- Direct Assignment (Users) --- //
|
|
1019
|
+
/**
|
|
1020
|
+
* Retrieve all roles assigned to a specific user.
|
|
1021
|
+
* @param userId - The target user ID.
|
|
1022
|
+
*/
|
|
1023
|
+
async getUserRoles(userId) {
|
|
1024
|
+
return this.client.get(`/api/v1/auth/users/${userId}/roles/`);
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Retrieve all permissions directly assigned to a specific user (excluding role-based permissions).
|
|
1028
|
+
* @param userId - The target user ID.
|
|
1029
|
+
*/
|
|
1030
|
+
async getUserPermissions(userId) {
|
|
1031
|
+
return this.client.get(`/api/v1/auth/users/${userId}/permissions/`);
|
|
1032
|
+
}
|
|
601
1033
|
/**
|
|
602
1034
|
* Attach a given Role globally to a user entity.
|
|
603
1035
|
* Use sparingly if B2B multi-tenancy contexts are preferred.
|
|
@@ -609,7 +1041,7 @@ var RbacModule = class {
|
|
|
609
1041
|
* Unbind a global Role from a user entity.
|
|
610
1042
|
*/
|
|
611
1043
|
async removeRoleFromUser(userId, roleCode) {
|
|
612
|
-
return this.client.delete(`/api/v1/auth/users/${userId}/roles/`, {
|
|
1044
|
+
return this.client.delete(`/api/v1/auth/users/${userId}/roles/`, void 0, {
|
|
613
1045
|
params: { role_code: roleCode }
|
|
614
1046
|
});
|
|
615
1047
|
}
|
|
@@ -623,9 +1055,7 @@ var RbacModule = class {
|
|
|
623
1055
|
* Ad-Hoc strip direct granular Permissions bindings from a specific User.
|
|
624
1056
|
*/
|
|
625
1057
|
async removePermissionsFromUser(userId, permissionCodes) {
|
|
626
|
-
return this.client.delete(`/api/v1/auth/users/${userId}/permissions/`, {
|
|
627
|
-
body: { permission_codes: permissionCodes }
|
|
628
|
-
});
|
|
1058
|
+
return this.client.delete(`/api/v1/auth/users/${userId}/permissions/`, { permission_codes: permissionCodes });
|
|
629
1059
|
}
|
|
630
1060
|
};
|
|
631
1061
|
|
|
@@ -652,6 +1082,7 @@ var UserModule = class {
|
|
|
652
1082
|
return this.client.patch("/api/v1/auth/me/", formData);
|
|
653
1083
|
}
|
|
654
1084
|
/**
|
|
1085
|
+
* @deprecated Use `gdpr.requestAccountDeletion()` instead. This proxy will be removed in a future release.
|
|
655
1086
|
* Trigger self-deletion of an entire account data boundary.
|
|
656
1087
|
* @param password - Requires the active system password as destructive proof of intent.
|
|
657
1088
|
* @param otpCode - (Optional) If an OTP was queried prior to attempting account deletion.
|
|
@@ -662,6 +1093,13 @@ var UserModule = class {
|
|
|
662
1093
|
otp_code: otpCode
|
|
663
1094
|
});
|
|
664
1095
|
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Retrieve the roles and permissions of the currently authenticated user.
|
|
1098
|
+
* @returns An object containing `roles[]` and `permissions[]`.
|
|
1099
|
+
*/
|
|
1100
|
+
async getMyRoles() {
|
|
1101
|
+
return this.client.get("/api/v1/auth/me/roles/");
|
|
1102
|
+
}
|
|
665
1103
|
// --- Admin Actions Mapping --- //
|
|
666
1104
|
/** (Admin only) Lists users paginated matching criteria. */
|
|
667
1105
|
async listUsers(params) {
|
|
@@ -786,8 +1224,9 @@ var B2bModule = class {
|
|
|
786
1224
|
|
|
787
1225
|
// src/modules/ai.ts
|
|
788
1226
|
var AiModule = class {
|
|
789
|
-
constructor(client) {
|
|
1227
|
+
constructor(client, logger) {
|
|
790
1228
|
this.client = client;
|
|
1229
|
+
this.logger = logger;
|
|
791
1230
|
this.client.addRequestInterceptor((config) => {
|
|
792
1231
|
const headers = { ...config.headers };
|
|
793
1232
|
if (this.agentToken) {
|
|
@@ -798,12 +1237,12 @@ var AiModule = class {
|
|
|
798
1237
|
}
|
|
799
1238
|
return { ...config, headers };
|
|
800
1239
|
});
|
|
801
|
-
this.client.addResponseInterceptor(async (response,
|
|
1240
|
+
this.client.addResponseInterceptor(async (response, _request) => {
|
|
802
1241
|
if (response.status === 202) {
|
|
803
1242
|
const cloned = response.clone();
|
|
804
1243
|
try {
|
|
805
1244
|
const data = await cloned.json();
|
|
806
|
-
|
|
1245
|
+
this.logger?.debug("[Tenxyte AI] Received 202 Awaiting Approval:", data);
|
|
807
1246
|
} catch {
|
|
808
1247
|
}
|
|
809
1248
|
} else if (response.status === 403) {
|
|
@@ -811,9 +1250,9 @@ var AiModule = class {
|
|
|
811
1250
|
try {
|
|
812
1251
|
const data = await cloned.json();
|
|
813
1252
|
if (data.code === "BUDGET_EXCEEDED") {
|
|
814
|
-
|
|
1253
|
+
this.logger?.warn("[Tenxyte AI] Network responded with Budget Exceeded for Agent.");
|
|
815
1254
|
} else if (data.status === "suspended") {
|
|
816
|
-
|
|
1255
|
+
this.logger?.warn("[Tenxyte AI] Circuit breaker open for Agent.");
|
|
817
1256
|
}
|
|
818
1257
|
} catch {
|
|
819
1258
|
}
|
|
@@ -823,6 +1262,7 @@ var AiModule = class {
|
|
|
823
1262
|
}
|
|
824
1263
|
agentToken = null;
|
|
825
1264
|
traceId = null;
|
|
1265
|
+
logger;
|
|
826
1266
|
// ─── AgentToken Lifecycle ───
|
|
827
1267
|
/**
|
|
828
1268
|
* Create an AgentToken granting specific deterministic limits to an AI Agent.
|
|
@@ -902,8 +1342,340 @@ var AiModule = class {
|
|
|
902
1342
|
}
|
|
903
1343
|
};
|
|
904
1344
|
|
|
1345
|
+
// src/modules/applications.ts
|
|
1346
|
+
var ApplicationsModule = class {
|
|
1347
|
+
constructor(client) {
|
|
1348
|
+
this.client = client;
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* List all registered applications (paginated).
|
|
1352
|
+
* @param params - Optional filters: `search`, `is_active`, `ordering`, `page`, `page_size`.
|
|
1353
|
+
* @returns A paginated list of applications.
|
|
1354
|
+
*/
|
|
1355
|
+
async listApplications(params) {
|
|
1356
|
+
return this.client.get("/api/v1/auth/applications/", {
|
|
1357
|
+
params
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Create a new application.
|
|
1362
|
+
* @param data - The application name and optional description.
|
|
1363
|
+
* @returns The created application including one-time `client_secret`.
|
|
1364
|
+
*/
|
|
1365
|
+
async createApplication(data) {
|
|
1366
|
+
return this.client.post("/api/v1/auth/applications/", data);
|
|
1367
|
+
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Get a single application by its ID.
|
|
1370
|
+
* @param appId - The application ID.
|
|
1371
|
+
* @returns The application details (secret is never included).
|
|
1372
|
+
*/
|
|
1373
|
+
async getApplication(appId) {
|
|
1374
|
+
return this.client.get(`/api/v1/auth/applications/${appId}/`);
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Fully update an application (PUT — all fields replaced).
|
|
1378
|
+
* @param appId - The application ID.
|
|
1379
|
+
* @param data - The full updated application data.
|
|
1380
|
+
* @returns The updated application.
|
|
1381
|
+
*/
|
|
1382
|
+
async updateApplication(appId, data) {
|
|
1383
|
+
return this.client.put(`/api/v1/auth/applications/${appId}/`, data);
|
|
1384
|
+
}
|
|
1385
|
+
/**
|
|
1386
|
+
* Partially update an application (PATCH — only provided fields are changed).
|
|
1387
|
+
* @param appId - The application ID.
|
|
1388
|
+
* @param data - The fields to update.
|
|
1389
|
+
* @returns The updated application.
|
|
1390
|
+
*/
|
|
1391
|
+
async patchApplication(appId, data) {
|
|
1392
|
+
return this.client.patch(`/api/v1/auth/applications/${appId}/`, data);
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Delete an application permanently.
|
|
1396
|
+
* @param appId - The application ID.
|
|
1397
|
+
*/
|
|
1398
|
+
async deleteApplication(appId) {
|
|
1399
|
+
return this.client.delete(`/api/v1/auth/applications/${appId}/`);
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Regenerate credentials for an application.
|
|
1403
|
+
* **Warning:** Old credentials are immediately invalidated. The new secret is shown only once.
|
|
1404
|
+
* @param appId - The application ID.
|
|
1405
|
+
* @param confirmation - Must be the string `"REGENERATE"` to confirm the irreversible action.
|
|
1406
|
+
* @returns The new credentials (access_key + access_secret shown once).
|
|
1407
|
+
*/
|
|
1408
|
+
async regenerateCredentials(appId, confirmation = "REGENERATE") {
|
|
1409
|
+
return this.client.post(`/api/v1/auth/applications/${appId}/regenerate/`, { confirmation });
|
|
1410
|
+
}
|
|
1411
|
+
};
|
|
1412
|
+
|
|
1413
|
+
// src/modules/admin.ts
|
|
1414
|
+
var AdminModule = class {
|
|
1415
|
+
constructor(client) {
|
|
1416
|
+
this.client = client;
|
|
1417
|
+
}
|
|
1418
|
+
// ─── Audit Logs ───
|
|
1419
|
+
/**
|
|
1420
|
+
* List audit log entries (paginated).
|
|
1421
|
+
* @param params - Optional filters and pagination.
|
|
1422
|
+
*/
|
|
1423
|
+
async listAuditLogs(params) {
|
|
1424
|
+
return this.client.get("/api/v1/auth/admin/audit-logs/", {
|
|
1425
|
+
params
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
/**
|
|
1429
|
+
* Get a single audit log entry by ID.
|
|
1430
|
+
* @param logId - The audit log entry ID.
|
|
1431
|
+
*/
|
|
1432
|
+
async getAuditLog(logId) {
|
|
1433
|
+
return this.client.get(`/api/v1/auth/admin/audit-logs/${logId}/`);
|
|
1434
|
+
}
|
|
1435
|
+
// ─── Login Attempts ───
|
|
1436
|
+
/**
|
|
1437
|
+
* List login attempt records (paginated).
|
|
1438
|
+
* @param params - Optional filters and pagination.
|
|
1439
|
+
*/
|
|
1440
|
+
async listLoginAttempts(params) {
|
|
1441
|
+
return this.client.get("/api/v1/auth/admin/login-attempts/", {
|
|
1442
|
+
params
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
// ─── Blacklisted Tokens ───
|
|
1446
|
+
/**
|
|
1447
|
+
* List blacklisted (revoked) JWT tokens (paginated).
|
|
1448
|
+
* @param params - Optional filters and pagination.
|
|
1449
|
+
*/
|
|
1450
|
+
async listBlacklistedTokens(params) {
|
|
1451
|
+
return this.client.get("/api/v1/auth/admin/blacklisted-tokens/", {
|
|
1452
|
+
params
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
/**
|
|
1456
|
+
* Remove expired blacklisted tokens.
|
|
1457
|
+
* @returns A summary object with cleanup results.
|
|
1458
|
+
*/
|
|
1459
|
+
async cleanupBlacklistedTokens() {
|
|
1460
|
+
return this.client.post("/api/v1/auth/admin/blacklisted-tokens/cleanup/");
|
|
1461
|
+
}
|
|
1462
|
+
// ─── Refresh Tokens ───
|
|
1463
|
+
/**
|
|
1464
|
+
* List refresh tokens (admin view — token values are hidden).
|
|
1465
|
+
* @param params - Optional filters and pagination.
|
|
1466
|
+
*/
|
|
1467
|
+
async listRefreshTokens(params) {
|
|
1468
|
+
return this.client.get("/api/v1/auth/admin/refresh-tokens/", {
|
|
1469
|
+
params
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Revoke a specific refresh token.
|
|
1474
|
+
* @param tokenId - The refresh token ID.
|
|
1475
|
+
* @returns The updated refresh token record.
|
|
1476
|
+
*/
|
|
1477
|
+
async revokeRefreshToken(tokenId) {
|
|
1478
|
+
return this.client.post(`/api/v1/auth/admin/refresh-tokens/${tokenId}/revoke/`);
|
|
1479
|
+
}
|
|
1480
|
+
};
|
|
1481
|
+
|
|
1482
|
+
// src/modules/gdpr.ts
|
|
1483
|
+
var GdprModule = class {
|
|
1484
|
+
constructor(client) {
|
|
1485
|
+
this.client = client;
|
|
1486
|
+
}
|
|
1487
|
+
// ─── User-facing ───
|
|
1488
|
+
/**
|
|
1489
|
+
* Request account deletion (GDPR-compliant).
|
|
1490
|
+
* Initiates a 30-day grace period during which the user can cancel.
|
|
1491
|
+
* @param data - Password (+ optional OTP code and reason).
|
|
1492
|
+
*/
|
|
1493
|
+
async requestAccountDeletion(data) {
|
|
1494
|
+
return this.client.post("/api/v1/auth/request-account-deletion/", data);
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Confirm the account deletion using the token received by email.
|
|
1498
|
+
* The token is valid for 24 hours. After confirmation the account enters the 30-day grace period.
|
|
1499
|
+
* @param token - The confirmation token from the email.
|
|
1500
|
+
*/
|
|
1501
|
+
async confirmAccountDeletion(token) {
|
|
1502
|
+
return this.client.post("/api/v1/auth/confirm-account-deletion/", { token });
|
|
1503
|
+
}
|
|
1504
|
+
/**
|
|
1505
|
+
* Cancel a pending account deletion during the grace period.
|
|
1506
|
+
* The account is immediately reactivated.
|
|
1507
|
+
* @param password - The current password for security.
|
|
1508
|
+
*/
|
|
1509
|
+
async cancelAccountDeletion(password) {
|
|
1510
|
+
return this.client.post("/api/v1/auth/cancel-account-deletion/", { password });
|
|
1511
|
+
}
|
|
1512
|
+
/**
|
|
1513
|
+
* Get the deletion status for the current user.
|
|
1514
|
+
* Includes pending, confirmed, or cancelled requests.
|
|
1515
|
+
*/
|
|
1516
|
+
async getAccountDeletionStatus() {
|
|
1517
|
+
return this.client.get("/api/v1/auth/account-deletion-status/");
|
|
1518
|
+
}
|
|
1519
|
+
/**
|
|
1520
|
+
* Export all personal data (GDPR right to data portability).
|
|
1521
|
+
* @param password - The current password for security.
|
|
1522
|
+
*/
|
|
1523
|
+
async exportUserData(password) {
|
|
1524
|
+
return this.client.post("/api/v1/auth/export-user-data/", { password });
|
|
1525
|
+
}
|
|
1526
|
+
// ─── Admin-facing ───
|
|
1527
|
+
/**
|
|
1528
|
+
* List deletion requests (admin, paginated).
|
|
1529
|
+
* @param params - Optional filters and pagination.
|
|
1530
|
+
*/
|
|
1531
|
+
async listDeletionRequests(params) {
|
|
1532
|
+
return this.client.get("/api/v1/auth/admin/deletion-requests/", {
|
|
1533
|
+
params
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Get a single deletion request by ID.
|
|
1538
|
+
* @param requestId - The deletion request ID.
|
|
1539
|
+
*/
|
|
1540
|
+
async getDeletionRequest(requestId) {
|
|
1541
|
+
return this.client.get(`/api/v1/auth/admin/deletion-requests/${requestId}/`);
|
|
1542
|
+
}
|
|
1543
|
+
/**
|
|
1544
|
+
* Process (execute) a confirmed deletion request.
|
|
1545
|
+
* **WARNING:** This is irreversible and permanently destroys all user data.
|
|
1546
|
+
* @param requestId - The deletion request ID.
|
|
1547
|
+
* @param data - Must include `{ confirmation: "PERMANENTLY DELETE" }`.
|
|
1548
|
+
*/
|
|
1549
|
+
async processDeletionRequest(requestId, data) {
|
|
1550
|
+
return this.client.post(`/api/v1/auth/admin/deletion-requests/${requestId}/process/`, data);
|
|
1551
|
+
}
|
|
1552
|
+
/**
|
|
1553
|
+
* Batch-process all confirmed deletion requests whose 30-day grace period has expired.
|
|
1554
|
+
* Typically run by a daily cron job.
|
|
1555
|
+
*/
|
|
1556
|
+
async processExpiredDeletions() {
|
|
1557
|
+
return this.client.post("/api/v1/auth/admin/deletion-requests/process-expired/");
|
|
1558
|
+
}
|
|
1559
|
+
};
|
|
1560
|
+
|
|
1561
|
+
// src/modules/dashboard.ts
|
|
1562
|
+
var DashboardModule = class {
|
|
1563
|
+
constructor(client) {
|
|
1564
|
+
this.client = client;
|
|
1565
|
+
}
|
|
1566
|
+
/**
|
|
1567
|
+
* Get global cross-module dashboard statistics.
|
|
1568
|
+
* Data varies based on the organizational context (`X-Org-Slug`) and permissions.
|
|
1569
|
+
* Covers users, authentication, applications, security, and GDPR metrics.
|
|
1570
|
+
* Charts span the last 7 days with previous-period comparisons.
|
|
1571
|
+
* @param params - Optional period and comparison flag.
|
|
1572
|
+
*/
|
|
1573
|
+
async getStats(params) {
|
|
1574
|
+
return this.client.get("/api/v1/auth/dashboard/stats/", {
|
|
1575
|
+
params
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
/**
|
|
1579
|
+
* Get authentication-specific statistics.
|
|
1580
|
+
* Includes login stats, methods breakdown, registrations, tokens, top failure reasons, and 7-day graphs.
|
|
1581
|
+
*/
|
|
1582
|
+
async getAuthStats() {
|
|
1583
|
+
return this.client.get("/api/v1/auth/dashboard/auth/");
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Get security-specific statistics.
|
|
1587
|
+
* Includes audit summary, blacklisted tokens, suspicious activity, and 2FA adoption.
|
|
1588
|
+
*/
|
|
1589
|
+
async getSecurityStats() {
|
|
1590
|
+
return this.client.get("/api/v1/auth/dashboard/security/");
|
|
1591
|
+
}
|
|
1592
|
+
/**
|
|
1593
|
+
* Get GDPR-specific statistics.
|
|
1594
|
+
* Includes deletion requests by status and data export metrics.
|
|
1595
|
+
*/
|
|
1596
|
+
async getGdprStats() {
|
|
1597
|
+
return this.client.get("/api/v1/auth/dashboard/gdpr/");
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* Get organization-specific statistics.
|
|
1601
|
+
* Includes organizations, members, roles, and top organizations.
|
|
1602
|
+
*/
|
|
1603
|
+
async getOrganizationStats() {
|
|
1604
|
+
return this.client.get("/api/v1/auth/dashboard/organizations/");
|
|
1605
|
+
}
|
|
1606
|
+
};
|
|
1607
|
+
|
|
1608
|
+
// src/utils/events.ts
|
|
1609
|
+
var EventEmitter = class {
|
|
1610
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
1611
|
+
events;
|
|
1612
|
+
constructor() {
|
|
1613
|
+
this.events = /* @__PURE__ */ new Map();
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Subscribe to an event.
|
|
1617
|
+
* @param event The event name
|
|
1618
|
+
* @param callback The callback function
|
|
1619
|
+
* @returns Unsubscribe function
|
|
1620
|
+
*/
|
|
1621
|
+
on(event, callback) {
|
|
1622
|
+
if (!this.events.has(event)) {
|
|
1623
|
+
this.events.set(event, []);
|
|
1624
|
+
}
|
|
1625
|
+
this.events.get(event).push(callback);
|
|
1626
|
+
return () => this.off(event, callback);
|
|
1627
|
+
}
|
|
1628
|
+
/**
|
|
1629
|
+
* Unsubscribe from an event.
|
|
1630
|
+
* @param event The event name
|
|
1631
|
+
* @param callback The exact callback function that was passed to .on()
|
|
1632
|
+
*/
|
|
1633
|
+
off(event, callback) {
|
|
1634
|
+
const callbacks = this.events.get(event);
|
|
1635
|
+
if (!callbacks) return;
|
|
1636
|
+
const index = callbacks.indexOf(callback);
|
|
1637
|
+
if (index !== -1) {
|
|
1638
|
+
callbacks.splice(index, 1);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
/**
|
|
1642
|
+
* Subscribe to an event exactly once.
|
|
1643
|
+
*/
|
|
1644
|
+
once(event, callback) {
|
|
1645
|
+
const wrapped = (payload) => {
|
|
1646
|
+
this.off(event, wrapped);
|
|
1647
|
+
callback(payload);
|
|
1648
|
+
};
|
|
1649
|
+
return this.on(event, wrapped);
|
|
1650
|
+
}
|
|
1651
|
+
/**
|
|
1652
|
+
* Emit an event internally.
|
|
1653
|
+
*/
|
|
1654
|
+
emit(event, payload) {
|
|
1655
|
+
const callbacks = this.events.get(event);
|
|
1656
|
+
if (!callbacks) return;
|
|
1657
|
+
const copy = [...callbacks];
|
|
1658
|
+
for (const callback of copy) {
|
|
1659
|
+
try {
|
|
1660
|
+
callback(payload);
|
|
1661
|
+
} catch (err) {
|
|
1662
|
+
console.error(`[Tenxyte EventEmitter] Error executing callback for event ${String(event)}`, err);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
removeAllListeners() {
|
|
1667
|
+
this.events.clear();
|
|
1668
|
+
}
|
|
1669
|
+
};
|
|
1670
|
+
|
|
905
1671
|
// src/client.ts
|
|
906
1672
|
var TenxyteClient = class {
|
|
1673
|
+
/** Fully resolved configuration (all defaults applied). */
|
|
1674
|
+
config;
|
|
1675
|
+
/** Persistent token storage back-end (defaults to MemoryStorage). */
|
|
1676
|
+
storage;
|
|
1677
|
+
/** Shared mutable context used by interceptors (org slug, agent trace ID). */
|
|
1678
|
+
context;
|
|
907
1679
|
/** The core HTTP wrapper handling network interception and parsing */
|
|
908
1680
|
http;
|
|
909
1681
|
/** Authentication module (Login, Signup, Magic link, session handling) */
|
|
@@ -918,8 +1690,22 @@ var TenxyteClient = class {
|
|
|
918
1690
|
b2b;
|
|
919
1691
|
/** AIRS - AI Responsibility & Security module (Agent tokens, Circuit breakers, HITL) */
|
|
920
1692
|
ai;
|
|
1693
|
+
/** Applications module (API client CRUD, credential management) */
|
|
1694
|
+
applications;
|
|
1695
|
+
/** Admin module (audit logs, login attempts, blacklisted tokens, refresh tokens) */
|
|
1696
|
+
admin;
|
|
1697
|
+
/** GDPR module (account deletion, data export, deletion request management) */
|
|
1698
|
+
gdpr;
|
|
1699
|
+
/** Dashboard module (global, auth, security, GDPR, organization statistics) */
|
|
1700
|
+
dashboard;
|
|
1701
|
+
/** Internal event emitter used via composition. */
|
|
1702
|
+
emitter;
|
|
921
1703
|
/**
|
|
922
1704
|
* Initializes the SDK with connection details for your Tenxyte-powered API.
|
|
1705
|
+
*
|
|
1706
|
+
* Accepts the full TenxyteClientConfig. Minimal usage with just { baseUrl }
|
|
1707
|
+
* is still supported for backward compatibility.
|
|
1708
|
+
*
|
|
923
1709
|
* @param options Configuration options including `baseUrl` and custom headers like `X-Access-Key`
|
|
924
1710
|
*
|
|
925
1711
|
* @example
|
|
@@ -931,22 +1717,166 @@ var TenxyteClient = class {
|
|
|
931
1717
|
* ```
|
|
932
1718
|
*/
|
|
933
1719
|
constructor(options) {
|
|
934
|
-
this.
|
|
935
|
-
this.
|
|
936
|
-
this.
|
|
1720
|
+
this.config = resolveConfig(options);
|
|
1721
|
+
this.storage = this.config.storage;
|
|
1722
|
+
this.emitter = new EventEmitter();
|
|
1723
|
+
this.context = { activeOrgSlug: null, agentTraceId: null };
|
|
1724
|
+
this.http = new TenxyteHttpClient({
|
|
1725
|
+
baseUrl: this.config.baseUrl,
|
|
1726
|
+
headers: this.config.headers,
|
|
1727
|
+
timeoutMs: this.config.timeoutMs
|
|
1728
|
+
});
|
|
1729
|
+
this.http.addRequestInterceptor(createAuthInterceptor(this.storage, this.context));
|
|
1730
|
+
if (this.config.autoDeviceInfo) {
|
|
1731
|
+
this.http.addRequestInterceptor(createDeviceInfoInterceptor(this.config.deviceInfoOverride));
|
|
1732
|
+
}
|
|
1733
|
+
if (this.config.retryConfig) {
|
|
1734
|
+
this.http.addResponseInterceptor(
|
|
1735
|
+
createRetryInterceptor(this.config.retryConfig, this.config.logger)
|
|
1736
|
+
);
|
|
1737
|
+
}
|
|
1738
|
+
if (this.config.autoRefresh) {
|
|
1739
|
+
this.http.addResponseInterceptor(
|
|
1740
|
+
createRefreshInterceptor(
|
|
1741
|
+
this.http,
|
|
1742
|
+
this.storage,
|
|
1743
|
+
() => {
|
|
1744
|
+
this.emit("session:expired", void 0);
|
|
1745
|
+
this.config.onSessionExpired?.();
|
|
1746
|
+
},
|
|
1747
|
+
(accessToken, refreshToken) => {
|
|
1748
|
+
this.rbac.setToken(accessToken);
|
|
1749
|
+
this.emit("token:refreshed", { accessToken });
|
|
1750
|
+
this.emit("token:stored", { accessToken, refreshToken });
|
|
1751
|
+
}
|
|
1752
|
+
)
|
|
1753
|
+
);
|
|
1754
|
+
}
|
|
937
1755
|
this.rbac = new RbacModule(this.http);
|
|
1756
|
+
this.auth = new AuthModule(
|
|
1757
|
+
this.http,
|
|
1758
|
+
this.storage,
|
|
1759
|
+
(accessToken, refreshToken) => {
|
|
1760
|
+
this.rbac.setToken(accessToken);
|
|
1761
|
+
this.emit("token:stored", { accessToken, refreshToken });
|
|
1762
|
+
},
|
|
1763
|
+
() => {
|
|
1764
|
+
this.rbac.setToken(null);
|
|
1765
|
+
this.emit("session:expired", void 0);
|
|
1766
|
+
}
|
|
1767
|
+
);
|
|
1768
|
+
this.security = new SecurityModule(this.http);
|
|
938
1769
|
this.user = new UserModule(this.http);
|
|
939
1770
|
this.b2b = new B2bModule(this.http);
|
|
940
|
-
this.ai = new AiModule(this.http);
|
|
1771
|
+
this.ai = new AiModule(this.http, this.config.logger);
|
|
1772
|
+
this.applications = new ApplicationsModule(this.http);
|
|
1773
|
+
this.admin = new AdminModule(this.http);
|
|
1774
|
+
this.gdpr = new GdprModule(this.http);
|
|
1775
|
+
this.dashboard = new DashboardModule(this.http);
|
|
1776
|
+
}
|
|
1777
|
+
// ─── Event delegation ───
|
|
1778
|
+
/** Subscribe to an SDK event. Returns an unsubscribe function. */
|
|
1779
|
+
on(event, callback) {
|
|
1780
|
+
return this.emitter.on(event, callback);
|
|
1781
|
+
}
|
|
1782
|
+
/** Subscribe to an SDK event exactly once. Returns an unsubscribe function. */
|
|
1783
|
+
once(event, callback) {
|
|
1784
|
+
return this.emitter.once(event, callback);
|
|
1785
|
+
}
|
|
1786
|
+
/** Unsubscribe a previously registered callback from an SDK event. */
|
|
1787
|
+
off(event, callback) {
|
|
1788
|
+
this.emitter.off(event, callback);
|
|
1789
|
+
}
|
|
1790
|
+
/** Emit an SDK event (internal use). */
|
|
1791
|
+
emit(event, payload) {
|
|
1792
|
+
this.emitter.emit(event, payload);
|
|
1793
|
+
}
|
|
1794
|
+
// ─── High-level helpers ───
|
|
1795
|
+
/**
|
|
1796
|
+
* Check whether a valid (non-expired) access token exists in storage.
|
|
1797
|
+
* Performs a synchronous JWT expiry check — no network call.
|
|
1798
|
+
*/
|
|
1799
|
+
async isAuthenticated() {
|
|
1800
|
+
const token = await this.storage.getItem("tx_access");
|
|
1801
|
+
if (!token) return false;
|
|
1802
|
+
return !this.isTokenExpiredSync(token);
|
|
1803
|
+
}
|
|
1804
|
+
/**
|
|
1805
|
+
* Return the current access token from storage, or `null` if absent.
|
|
1806
|
+
*/
|
|
1807
|
+
async getAccessToken() {
|
|
1808
|
+
return this.storage.getItem("tx_access");
|
|
1809
|
+
}
|
|
1810
|
+
/**
|
|
1811
|
+
* Decode the current access token and return the JWT payload.
|
|
1812
|
+
* Returns `null` if no token is stored or if decoding fails.
|
|
1813
|
+
* No network call is made — this reads from the cached JWT.
|
|
1814
|
+
*/
|
|
1815
|
+
async getCurrentUser() {
|
|
1816
|
+
const token = await this.storage.getItem("tx_access");
|
|
1817
|
+
if (!token) return null;
|
|
1818
|
+
return decodeJwt(token);
|
|
1819
|
+
}
|
|
1820
|
+
/**
|
|
1821
|
+
* Check whether the stored access token is expired without making a network call.
|
|
1822
|
+
* Returns `true` if expired or if no token is present.
|
|
1823
|
+
*/
|
|
1824
|
+
async isTokenExpired() {
|
|
1825
|
+
const token = await this.storage.getItem("tx_access");
|
|
1826
|
+
if (!token) return true;
|
|
1827
|
+
return this.isTokenExpiredSync(token);
|
|
1828
|
+
}
|
|
1829
|
+
/** Synchronous helper: checks JWT `exp` claim against current time. */
|
|
1830
|
+
isTokenExpiredSync(token) {
|
|
1831
|
+
const decoded = decodeJwt(token);
|
|
1832
|
+
if (!decoded?.exp) return true;
|
|
1833
|
+
return decoded.exp * 1e3 < Date.now() - 3e4;
|
|
1834
|
+
}
|
|
1835
|
+
// ─── Framework wrapper interface ───
|
|
1836
|
+
/**
|
|
1837
|
+
* Returns a synchronous snapshot of the SDK state.
|
|
1838
|
+
* Designed for consumption by framework wrappers (React, Vue, etc.).
|
|
1839
|
+
* Note: This is async because storage access may be async.
|
|
1840
|
+
*/
|
|
1841
|
+
async getState() {
|
|
1842
|
+
const token = await this.storage.getItem("tx_access");
|
|
1843
|
+
const isAuthenticated = token ? !this.isTokenExpiredSync(token) : false;
|
|
1844
|
+
const user = token ? decodeJwt(token) : null;
|
|
1845
|
+
return {
|
|
1846
|
+
isAuthenticated,
|
|
1847
|
+
user,
|
|
1848
|
+
accessToken: token,
|
|
1849
|
+
activeOrg: this.context.activeOrgSlug,
|
|
1850
|
+
isAgentMode: this.ai.isAgentMode()
|
|
1851
|
+
};
|
|
941
1852
|
}
|
|
942
1853
|
};
|
|
943
1854
|
// Annotate the CommonJS export names for ESM import in node:
|
|
944
1855
|
0 && (module.exports = {
|
|
1856
|
+
AdminModule,
|
|
1857
|
+
AiModule,
|
|
1858
|
+
ApplicationsModule,
|
|
945
1859
|
AuthModule,
|
|
1860
|
+
B2bModule,
|
|
1861
|
+
CookieStorage,
|
|
1862
|
+
DashboardModule,
|
|
1863
|
+
EventEmitter,
|
|
1864
|
+
GdprModule,
|
|
1865
|
+
LocalStorage,
|
|
1866
|
+
MemoryStorage,
|
|
1867
|
+
NOOP_LOGGER,
|
|
946
1868
|
RbacModule,
|
|
1869
|
+
SDK_VERSION,
|
|
947
1870
|
SecurityModule,
|
|
948
1871
|
TenxyteClient,
|
|
949
1872
|
TenxyteHttpClient,
|
|
950
|
-
UserModule
|
|
1873
|
+
UserModule,
|
|
1874
|
+
buildDeviceInfo,
|
|
1875
|
+
createAuthInterceptor,
|
|
1876
|
+
createDeviceInfoInterceptor,
|
|
1877
|
+
createRefreshInterceptor,
|
|
1878
|
+
createRetryInterceptor,
|
|
1879
|
+
decodeJwt,
|
|
1880
|
+
resolveConfig
|
|
951
1881
|
});
|
|
952
1882
|
//# sourceMappingURL=index.cjs.map
|