@simplr-ai/node 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Simplr's **server-side** SDK for Node.js — run fraud/identity checks, score orders, ingest edge logs, and verify webhook signatures, all with your secret key.
4
4
 
5
- > This is the backend SDK. For client-side device signals, RUM, and feature-flag evaluation use [`@simplr-ai/fraud-sdk`](https://www.npmjs.com/package/@simplr-ai/fraud-sdk) (browser) or `simplify_fraud` (Flutter).
5
+ > This is the backend SDK. For client-side device signals, RUM, and feature-flag evaluation use [`@simplr-ai/js`](https://www.npmjs.com/package/@simplr-ai/js) (browser) or `simplr_fraud` (Flutter).
6
6
 
7
7
  Docs: https://docs.simplr.so/docs/sdks/node
8
8
 
@@ -73,7 +73,7 @@ import { Simplr } from "@simplr-ai/node";
73
73
  const simplr = new Simplr({ apiKey: process.env.SIMPLR_API_KEY! });
74
74
  const app = express();
75
75
 
76
- app.post("/hooks/simplify", express.raw({ type: "application/json" }), (req, res) => {
76
+ app.post("/hooks/simplr", express.raw({ type: "application/json" }), (req, res) => {
77
77
  const sig = req.header("X-Simplr-Signature")!;
78
78
  try {
79
79
  const event = simplr.webhooks.constructEvent(req.body, sig, process.env.SIMPLR_WEBHOOK_SECRET!);
@@ -106,8 +106,100 @@ if (simplr.flags.isEnabled("new-checkout")) {
106
106
  simplr.flags.isEnabled("beta", { userId: "u1", attributes: { plan: "growth" } });
107
107
  ```
108
108
 
109
+ ### Targeting a named environment
110
+
111
+ `environment` accepts a named environment slug (e.g. `"dev"`, `"uat"`, `"prod"`) as well as the legacy `"live"`/`"test"` key modes. When omitted, the API falls back to the public key's own live/test mode.
112
+
113
+ ```ts
114
+ // On the Simplr client (forwarded to simplr.flags):
115
+ const simplr = new Simplr({ apiKey, publicKey, environment: "uat" });
116
+
117
+ // Or standalone:
118
+ import { SimplrFlags } from "@simplr-ai/node";
119
+ const flags = new SimplrFlags({ publicKey, environment: "uat" });
120
+ await flags.initialize();
121
+ ```
122
+
109
123
  You can also use `SimplrFlags` standalone.
110
124
 
125
+ ## Profiles (`simplr.profiles`)
126
+
127
+ Anonymous user profiles + order fraud monitoring. Identify a user, score orders, read a user's risk, and report outcomes back to improve scoring.
128
+
129
+ ```ts
130
+ // Create/update an anonymous profile and (optionally) link a device.
131
+ const { profile, is_new } = await simplr.profiles.identify("user-123", {
132
+ profileType: "customer",
133
+ fingerprintHash: "9f2a…", // from a client device-signal collector
134
+ });
135
+
136
+ // Score an order.
137
+ const result = await simplr.profiles.submitOrder({
138
+ order_id: "order-1",
139
+ external_id: "user-123",
140
+ amount: 4999,
141
+ currency: "USD",
142
+ });
143
+
144
+ // Read a user's current risk profile.
145
+ const risk = await simplr.profiles.getProfileRisk("user-123");
146
+
147
+ // Feed back a confirmed outcome (chargeback, manual review, …).
148
+ await simplr.profiles.reportOutcome("user-123", "fraud"); // or "legitimate"
149
+ ```
150
+
151
+ ## RUM (`simplr.rum`)
152
+
153
+ Server-side Real User Monitoring. Events are batched and flushed to `/v1/rum/events`. The flush timer is installed with `unref()`, so it never keeps your process alive. There is no DOM auto-capture on the server — report views/actions/errors/logs explicitly.
154
+
155
+ ```ts
156
+ simplr.rum.initialize({ applicationId: "my-api", environment: "production" });
157
+
158
+ simplr.rum.setUser("user-123", { plan: "pro" });
159
+ simplr.rum.addAttribute("region", "eu-west-1");
160
+
161
+ simplr.rum.trackView("POST /checkout");
162
+ simplr.rum.trackAction("charge_card", { gateway: "stripe" });
163
+ simplr.rum.log("info", "checkout completed", { orderId: "order-1" });
164
+
165
+ try {
166
+ // …
167
+ } catch (err) {
168
+ simplr.rum.trackError(err as Error);
169
+ }
170
+
171
+ await simplr.rum.flush(); // force a flush
172
+ await simplr.rum.stopSession(); // emit session_end, flush, stop timer
173
+ ```
174
+
175
+ ## AI delegation (`simplr.ai`)
176
+
177
+ OAuth-like AI authentication — mint, validate, and revoke delegation tokens that an end user shares with their AI agent.
178
+
179
+ ```ts
180
+ // Mint a token (only returned once).
181
+ const delegation = await simplr.ai.createDelegation({
182
+ userId: "user-123",
183
+ binding: "verified_device",
184
+ expiresInDays: 7,
185
+ fingerprintHash: "9f2a…",
186
+ });
187
+
188
+ // Validate (introspect) an incoming token on your AI gateway.
189
+ const check = await simplr.ai.validate(token, { aiProvider: "anthropic", action: "read_orders" });
190
+ if (!check.valid) { /* reject */ }
191
+
192
+ // Manage delegations.
193
+ await simplr.ai.list("user-123");
194
+ await simplr.ai.get(delegation.delegationId);
195
+ await simplr.ai.stats();
196
+ await simplr.ai.revoke(delegation.delegationId, "user revoked");
197
+ await simplr.ai.revokeAllForUser("user-123", "logout"); // returns count
198
+
199
+ ```
200
+
201
+ > The browser SDK's interactive `connect()` popup flow is web-only and is intentionally omitted from the server SDK.
202
+
111
203
  ## Admin / measurement (`SimplrAdmin`)
112
204
 
113
205
  Dashboard operations — usage/measurement, feature-flag CRUD, and RUM analytics — require a **portal token** (JWT), not an API key:
package/dist/index.cjs CHANGED
@@ -21,9 +21,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
23
  Simplr: () => Simplr,
24
+ SimplrAI: () => SimplrAI,
24
25
  SimplrAdmin: () => SimplrAdmin,
25
26
  SimplrError: () => SimplrError,
26
27
  SimplrFlags: () => SimplrFlags,
28
+ SimplrProfiles: () => SimplrProfiles,
29
+ SimplrRUM: () => SimplrRUM,
27
30
  WebhookVerificationError: () => WebhookVerificationError,
28
31
  constructWebhookEvent: () => constructEvent,
29
32
  default: () => src_default,
@@ -210,7 +213,7 @@ var SimplrFlags = class {
210
213
  }
211
214
  /** Re-fetch the flag config (counts as one billable request). */
212
215
  async refresh() {
213
- const path = this.environment ? `/v1/flags?environment=${this.environment}` : "/v1/flags";
216
+ const path = this.environment ? `/v1/flags?environment=${encodeURIComponent(this.environment)}` : "/v1/flags";
214
217
  try {
215
218
  const content = await apiRequest(this.cfg, "GET", path);
216
219
  const list = content?.flags || [];
@@ -246,6 +249,295 @@ var SimplrFlags = class {
246
249
  }
247
250
  };
248
251
 
252
+ // src/profiles.ts
253
+ var SimplrProfiles = class {
254
+ constructor(cfg) {
255
+ this.cfg = cfg;
256
+ }
257
+ cfg;
258
+ /**
259
+ * Identify a user — creates or updates an anonymous profile and (optionally)
260
+ * links a device fingerprint. POST /v1/profiles.
261
+ */
262
+ identify(externalId, options) {
263
+ const { profileType, fingerprintHash, ...rest } = options ?? {};
264
+ const body = {
265
+ external_id: externalId,
266
+ profile_type: profileType || "customer",
267
+ ...rest
268
+ };
269
+ if (fingerprintHash) body.fingerprint_hash = fingerprintHash;
270
+ return apiRequest(this.cfg, "POST", "/v1/profiles", body);
271
+ }
272
+ /** Submit an order for real-time fraud scoring. POST /v1/orders. */
273
+ submitOrder(order) {
274
+ return apiRequest(this.cfg, "POST", "/v1/orders", order);
275
+ }
276
+ /** Get the risk profile for a user. GET /v1/profiles/{externalId}. */
277
+ getProfileRisk(externalId) {
278
+ return apiRequest(
279
+ this.cfg,
280
+ "GET",
281
+ `/v1/profiles/${encodeURIComponent(externalId)}`
282
+ );
283
+ }
284
+ /** Report a profile as fraud or legitimate. POST /v1/profiles/{externalId}/outcome. */
285
+ async reportOutcome(externalId, outcome) {
286
+ await apiRequest(
287
+ this.cfg,
288
+ "POST",
289
+ `/v1/profiles/${encodeURIComponent(externalId)}/outcome`,
290
+ { outcome }
291
+ );
292
+ }
293
+ };
294
+
295
+ // src/rum.ts
296
+ var DEFAULT_BATCH_SIZE = 30;
297
+ var DEFAULT_FLUSH_INTERVAL = 1e4;
298
+ var DEFAULT_ENDPOINT = "/v1/rum/events";
299
+ function genId() {
300
+ return Date.now().toString(36) + Math.random().toString(36).slice(2, 10);
301
+ }
302
+ var SimplrRUM = class {
303
+ constructor(cfg) {
304
+ this.cfg = cfg;
305
+ }
306
+ cfg;
307
+ config = null;
308
+ initialized = false;
309
+ queue = [];
310
+ timer = null;
311
+ flushing = false;
312
+ sessionId = null;
313
+ currentViewId = null;
314
+ userId;
315
+ userAttributes;
316
+ globalAttributes = {};
317
+ batchSize = DEFAULT_BATCH_SIZE;
318
+ endpoint = DEFAULT_ENDPOINT;
319
+ /** Initialize the SDK, start a session, and begin the flush timer. */
320
+ initialize(config) {
321
+ if (this.initialized) return;
322
+ this.config = config;
323
+ this.batchSize = config.batchSize ?? DEFAULT_BATCH_SIZE;
324
+ this.endpoint = config.endpoint ?? DEFAULT_ENDPOINT;
325
+ this.sessionId = genId();
326
+ this.initialized = true;
327
+ this.trackEvent("session_start");
328
+ const interval = config.flushInterval ?? DEFAULT_FLUSH_INTERVAL;
329
+ if (interval > 0) {
330
+ this.timer = setInterval(() => {
331
+ void this.flush();
332
+ }, interval);
333
+ this.timer?.unref?.();
334
+ }
335
+ }
336
+ isInitialized() {
337
+ return this.initialized;
338
+ }
339
+ /** Associate subsequent events with a user. */
340
+ setUser(userId, attributes) {
341
+ this.userId = userId;
342
+ this.userAttributes = attributes;
343
+ }
344
+ clearUser() {
345
+ this.userId = void 0;
346
+ this.userAttributes = void 0;
347
+ }
348
+ addAttribute(key, value) {
349
+ this.globalAttributes[key] = value;
350
+ }
351
+ removeAttribute(key) {
352
+ delete this.globalAttributes[key];
353
+ }
354
+ /** Track a screen/page view. */
355
+ trackView(name, attributes) {
356
+ if (!this.initialized) return;
357
+ this.currentViewId = genId();
358
+ this.trackEvent("view", {
359
+ view: { id: this.currentViewId, name },
360
+ attributes
361
+ });
362
+ }
363
+ /** Track a user action. */
364
+ trackAction(name, attributes) {
365
+ if (!this.initialized) return;
366
+ this.trackEvent("action", { action: { name, type: "custom" }, attributes });
367
+ }
368
+ /** Track an error. */
369
+ trackError(error, attributes) {
370
+ if (!this.initialized) return;
371
+ const data = error instanceof Error ? { message: error.message, stack: error.stack, type: error.constructor.name } : error;
372
+ this.trackEvent("error", { error: data, attributes });
373
+ }
374
+ /** Emit a log line. */
375
+ log(level, message, attributes) {
376
+ if (!this.initialized) return;
377
+ this.trackEvent("log", { log: { level, message }, attributes });
378
+ }
379
+ trackEvent(type, data) {
380
+ if (!this.initialized || !this.sessionId) return;
381
+ const event = {
382
+ type,
383
+ timestamp: Date.now(),
384
+ sessionId: this.sessionId,
385
+ viewId: this.currentViewId || void 0,
386
+ userId: this.userId,
387
+ applicationId: this.config.applicationId,
388
+ applicationVersion: this.config?.applicationVersion,
389
+ environment: this.config?.environment,
390
+ userAttributes: this.userAttributes,
391
+ globalAttributes: Object.keys(this.globalAttributes).length > 0 ? this.globalAttributes : void 0,
392
+ ...data
393
+ };
394
+ this.queue.push(event);
395
+ if (this.queue.length >= this.batchSize) void this.flush();
396
+ }
397
+ /** Flush queued events to POST /v1/rum/events. */
398
+ async flush() {
399
+ if (this.flushing || this.queue.length === 0) return;
400
+ this.flushing = true;
401
+ const events = this.queue;
402
+ this.queue = [];
403
+ try {
404
+ await apiRequest(this.cfg, "POST", this.endpoint, {
405
+ events,
406
+ sentAt: Date.now()
407
+ });
408
+ } catch {
409
+ this.queue = [...events, ...this.queue];
410
+ } finally {
411
+ this.flushing = false;
412
+ }
413
+ }
414
+ /** End the session, flush remaining events, and stop the timer. */
415
+ async stopSession() {
416
+ if (!this.initialized) return;
417
+ this.trackEvent("session_end");
418
+ await this.flush();
419
+ if (this.timer) {
420
+ clearInterval(this.timer);
421
+ this.timer = null;
422
+ }
423
+ this.initialized = false;
424
+ }
425
+ getSessionId() {
426
+ return this.sessionId;
427
+ }
428
+ getViewId() {
429
+ return this.currentViewId;
430
+ }
431
+ };
432
+
433
+ // src/ai.ts
434
+ function mapDelegation(d) {
435
+ return {
436
+ delegationId: d.delegation_id,
437
+ endUserId: d.end_user_id,
438
+ bindingMode: d.binding_mode,
439
+ status: d.status,
440
+ expiresAt: d.expires_at,
441
+ useCount: d.use_count,
442
+ lastUsedAt: d.last_used_at,
443
+ createdAt: d.created_at
444
+ };
445
+ }
446
+ var SimplrAI = class {
447
+ constructor(cfg) {
448
+ this.cfg = cfg;
449
+ }
450
+ cfg;
451
+ /** Create a new AI delegation token for a user. POST /v1/ai/delegations. */
452
+ async createDelegation(options) {
453
+ const content = await apiRequest(this.cfg, "POST", "/v1/ai/delegations", {
454
+ end_user_id: options.userId,
455
+ end_user_email: options.email,
456
+ binding: options.binding || "any_location",
457
+ expires_in_days: options.expiresInDays || 7,
458
+ session_id: options.sessionId,
459
+ fingerprint_hash: options.fingerprintHash
460
+ });
461
+ const d = content.delegation;
462
+ return {
463
+ token: d.token,
464
+ delegationId: d.delegation_id,
465
+ expiresAt: d.expires_at,
466
+ bindingMode: d.binding_mode
467
+ };
468
+ }
469
+ /** Validate (introspect) an AI delegation token. POST /v1/ai/validate. */
470
+ async validate(token, options) {
471
+ try {
472
+ const content = await apiRequest(this.cfg, "POST", "/v1/ai/validate", {
473
+ token,
474
+ fingerprint_hash: options?.fingerprintHash,
475
+ ai_provider: options?.aiProvider,
476
+ action: options?.action
477
+ });
478
+ return {
479
+ valid: true,
480
+ sessionType: content.session_type,
481
+ endUserId: content.end_user_id,
482
+ delegation: content.delegation ? {
483
+ delegationId: content.delegation.delegation_id,
484
+ bindingMode: content.delegation.binding_mode,
485
+ expiresAt: content.delegation.expires_at,
486
+ useCount: content.delegation.use_count
487
+ } : void 0
488
+ };
489
+ } catch (err) {
490
+ return { valid: false, error: err instanceof Error ? err.message : "Validation failed" };
491
+ }
492
+ }
493
+ /** Revoke a delegation. POST /v1/ai/delegations/{id}/revoke. */
494
+ async revoke(delegationId, reason) {
495
+ await apiRequest(
496
+ this.cfg,
497
+ "POST",
498
+ `/v1/ai/delegations/${encodeURIComponent(delegationId)}/revoke`,
499
+ { reason }
500
+ );
501
+ }
502
+ /** List delegations, optionally filtered by user. GET /v1/ai/delegations. */
503
+ async list(userId) {
504
+ const path = userId ? `/v1/ai/delegations?end_user_id=${encodeURIComponent(userId)}` : "/v1/ai/delegations";
505
+ const content = await apiRequest(this.cfg, "GET", path);
506
+ return (content.delegations || []).map(mapDelegation);
507
+ }
508
+ /** Get a single delegation. GET /v1/ai/delegations/{id}. */
509
+ async get(delegationId) {
510
+ const content = await apiRequest(
511
+ this.cfg,
512
+ "GET",
513
+ `/v1/ai/delegations/${encodeURIComponent(delegationId)}`
514
+ );
515
+ return mapDelegation(content.delegation);
516
+ }
517
+ /** Get delegation statistics. GET /v1/ai/stats. */
518
+ async stats() {
519
+ const content = await apiRequest(this.cfg, "GET", "/v1/ai/stats");
520
+ const s = content.stats;
521
+ return {
522
+ totalDelegations: s.total_delegations,
523
+ activeDelegations: s.active_delegations,
524
+ totalUses: s.total_uses,
525
+ delegationsByBinding: {
526
+ verifiedDevice: s.delegations_by_binding.verified_device,
527
+ anyLocation: s.delegations_by_binding.any_location
528
+ }
529
+ };
530
+ }
531
+ /** Revoke all delegations for a user (e.g. on logout). POST /v1/ai/revoke-all. */
532
+ async revokeAllForUser(userId, reason) {
533
+ const content = await apiRequest(this.cfg, "POST", "/v1/ai/revoke-all", {
534
+ end_user_id: userId,
535
+ reason
536
+ });
537
+ return content.revoked_count;
538
+ }
539
+ };
540
+
249
541
  // src/webhooks.ts
250
542
  var webhooks_exports = {};
251
543
  __export(webhooks_exports, {
@@ -392,6 +684,12 @@ var Simplr = class {
392
684
  orders;
393
685
  phone;
394
686
  edge;
687
+ /** Anonymous user profiles + order fraud monitoring. */
688
+ profiles;
689
+ /** Real User Monitoring — batched events to /v1/rum/events. */
690
+ rum;
691
+ /** AI delegation — OAuth-like AI authentication. */
692
+ ai;
395
693
  /** Webhook signature helpers (no network). */
396
694
  webhooks = webhooks_exports;
397
695
  _flags;
@@ -409,9 +707,13 @@ var Simplr = class {
409
707
  this.orders = new OrdersResource(this.cfg);
410
708
  this.phone = new PhoneResource(this.cfg);
411
709
  this.edge = new EdgeResource(this.cfg);
710
+ this.profiles = new SimplrProfiles(this.cfg);
711
+ this.rum = new SimplrRUM(this.cfg);
712
+ this.ai = new SimplrAI(this.cfg);
412
713
  if (options.publicKey) {
413
714
  this._flags = new SimplrFlags({
414
715
  publicKey: options.publicKey,
716
+ environment: options.environment,
415
717
  baseUrl: this.cfg.baseUrl,
416
718
  timeoutMs: this.cfg.timeoutMs,
417
719
  fetch: this.cfg.fetchImpl
@@ -443,9 +745,12 @@ var src_default = Simplr;
443
745
  // Annotate the CommonJS export names for ESM import in node:
444
746
  0 && (module.exports = {
445
747
  Simplr,
748
+ SimplrAI,
446
749
  SimplrAdmin,
447
750
  SimplrError,
448
751
  SimplrFlags,
752
+ SimplrProfiles,
753
+ SimplrRUM,
449
754
  WebhookVerificationError,
450
755
  constructWebhookEvent,
451
756
  verifyWebhook