@edge-markets/connect-node 1.7.0 → 1.8.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/dist/index.mjs CHANGED
@@ -8,7 +8,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
8
8
  // src/edge-connect-server.ts
9
9
  import {
10
10
  EdgeApiError,
11
- EdgeAuthenticationError,
11
+ EdgeAuthenticationError as EdgeAuthenticationError2,
12
12
  EdgeConsentRequiredError,
13
13
  EdgeError,
14
14
  EdgeIdentityVerificationError,
@@ -19,12 +19,137 @@ import {
19
19
  getEnvironmentConfig
20
20
  } from "@edge-markets/connect";
21
21
 
22
+ // src/transfer-helpers.ts
23
+ import {
24
+ EdgeValidationError
25
+ } from "@edge-markets/connect";
26
+ import crypto from "crypto";
27
+ var DEFAULT_MIN_AMOUNT = "1.00";
28
+ var DEFAULT_MAX_AMOUNT = "10000.00";
29
+ function normalizeMoneyAmount(value) {
30
+ const cents = parseMoneyToCents(value, "amount");
31
+ const dollars = cents / 100n;
32
+ const remainder = cents % 100n;
33
+ return `${dollars.toString()}.${remainder.toString().padStart(2, "0")}`;
34
+ }
35
+ function validateTransferAmount(value, limits = {}) {
36
+ const amount = parseMoneyToCents(value, "amount");
37
+ const min = parseMoneyToCents(limits.min ?? DEFAULT_MIN_AMOUNT, "min");
38
+ const max = parseMoneyToCents(limits.max ?? DEFAULT_MAX_AMOUNT, "max");
39
+ const errors = {};
40
+ if (min <= 0n) errors.min = ["Minimum amount must be greater than 0"];
41
+ if (max < min) errors.max = ["Maximum amount must be greater than or equal to minimum amount"];
42
+ if (amount < min) errors.amount = [`Must be at least ${formatCents(min)}`];
43
+ if (amount > max) errors.amount = [`Must be at most ${formatCents(max)}`];
44
+ if (Object.keys(errors).length > 0) {
45
+ throw new EdgeValidationError("Invalid transfer amount", errors);
46
+ }
47
+ return formatCents(amount);
48
+ }
49
+ function createTransferIdempotencyKey(options) {
50
+ const namespace = sanitizeKeyPart(options.namespace || "edge");
51
+ const partnerUserId = requireKeyPart(options.partnerUserId, "partnerUserId");
52
+ const externalId = options.externalId ? sanitizeKeyPart(options.externalId) : void 0;
53
+ const nonce = options.nonce ? sanitizeKeyPart(options.nonce) : void 0;
54
+ const amount = normalizeMoneyAmount(options.amount);
55
+ const digest = crypto.createHash("sha256").update(
56
+ JSON.stringify({
57
+ partnerUserId,
58
+ type: options.type,
59
+ amount,
60
+ category: options.category ?? null,
61
+ externalId: externalId ?? null,
62
+ nonce: nonce ?? null
63
+ })
64
+ ).digest("hex").slice(0, 32);
65
+ return [namespace, options.type, digest].join("_");
66
+ }
67
+ function assertSameTransferIntent(existing, requested) {
68
+ const errors = {};
69
+ if (existing.type !== requested.type) {
70
+ errors.type = [`Expected ${existing.type}, received ${requested.type}`];
71
+ }
72
+ const existingAmount = normalizeMoneyAmount(existing.amount);
73
+ const requestedAmount = normalizeMoneyAmount(requested.amount);
74
+ if (existingAmount !== requestedAmount) {
75
+ errors.amount = [`Expected ${existingAmount}, received ${requestedAmount}`];
76
+ }
77
+ const existingCategory = existing.category ?? void 0;
78
+ const requestedCategory = requested.category ?? void 0;
79
+ if (existingCategory !== requestedCategory) {
80
+ errors.category = [`Expected ${existingCategory || "unset"}, received ${requestedCategory || "unset"}`];
81
+ }
82
+ if (Object.keys(errors).length > 0) {
83
+ throw new EdgeValidationError("Transfer intent does not match existing idempotent transfer", errors);
84
+ }
85
+ }
86
+ async function startTransferVerification(client, options) {
87
+ const origin = typeof options.origin === "string" ? options.origin.trim() : "";
88
+ if (!origin) {
89
+ throw new EdgeValidationError("Verification origin is required", {
90
+ origin: ["Required"]
91
+ });
92
+ }
93
+ const transfer = await client.initiateTransfer({
94
+ ...options.transfer,
95
+ amount: normalizeMoneyAmount(options.transfer.amount)
96
+ });
97
+ const transferId = transfer.transferId;
98
+ if (!transferId) {
99
+ throw new EdgeValidationError("Transfer response missing transferId", {
100
+ transferId: ["Required to create a verification session"]
101
+ });
102
+ }
103
+ const verificationSession = await client.createVerificationSession(transferId, { origin });
104
+ return { transfer, verificationSession };
105
+ }
106
+ function parseMoneyToCents(value, field) {
107
+ let input;
108
+ if (typeof value === "number") {
109
+ if (!Number.isFinite(value)) {
110
+ throw new EdgeValidationError("Money amount must be finite", { [field]: ["Must be a finite number"] });
111
+ }
112
+ input = value.toFixed(2);
113
+ } else if (typeof value === "string") {
114
+ input = value.trim();
115
+ } else {
116
+ throw new EdgeValidationError("Money amount must be a string or number", { [field]: ["Invalid type"] });
117
+ }
118
+ if (!/^\d+(\.\d{0,2})?$/.test(input)) {
119
+ throw new EdgeValidationError("Money amount must be a positive decimal with at most 2 places", {
120
+ [field]: ['Use a decimal string like "25.00"']
121
+ });
122
+ }
123
+ const [dollars, cents = ""] = input.split(".");
124
+ const normalizedDollars = dollars.replace(/^0+(?=\d)/, "") || "0";
125
+ const amount = BigInt(normalizedDollars) * 100n + BigInt(cents.padEnd(2, "0"));
126
+ if (amount <= 0n) {
127
+ throw new EdgeValidationError("Money amount must be greater than 0", { [field]: ["Must be greater than 0"] });
128
+ }
129
+ return amount;
130
+ }
131
+ function formatCents(cents) {
132
+ return `${(cents / 100n).toString()}.${(cents % 100n).toString().padStart(2, "0")}`;
133
+ }
134
+ function requireKeyPart(value, field) {
135
+ const sanitized = sanitizeKeyPart(value);
136
+ if (!sanitized) {
137
+ throw new EdgeValidationError("Idempotency key input is incomplete", {
138
+ [field]: ["Required"]
139
+ });
140
+ }
141
+ return sanitized;
142
+ }
143
+ function sanitizeKeyPart(value) {
144
+ return value.trim().replace(/[^a-zA-Z0-9_.:-]/g, "_").slice(0, 64);
145
+ }
146
+
22
147
  // src/validators.ts
23
- import { EdgeValidationError } from "@edge-markets/connect";
148
+ import { EdgeValidationError as EdgeValidationError2 } from "@edge-markets/connect";
24
149
  var UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
25
150
  function validateTransferId(transferId) {
26
151
  if (typeof transferId !== "string" || !UUID_V4_REGEX.test(transferId)) {
27
- throw new EdgeValidationError("transferId must be a UUID v4", {
152
+ throw new EdgeValidationError2("transferId must be a UUID v4", {
28
153
  transferId: ["Must be a valid UUID v4"]
29
154
  });
30
155
  }
@@ -44,7 +169,7 @@ function validateVerifyIdentityOptions(options) {
44
169
  }
45
170
  if (Object.keys(errors).length > 0) {
46
171
  const firstMessage = Object.values(errors)[0][0];
47
- throw new EdgeValidationError(firstMessage, errors);
172
+ throw new EdgeValidationError2(firstMessage, errors);
48
173
  }
49
174
  }
50
175
  function validateTransferOptions(options) {
@@ -73,7 +198,7 @@ function validateTransferOptions(options) {
73
198
  errors.category = ["Must be one of: sportsbook, casino, dfs, sweepstakes"];
74
199
  }
75
200
  if (Object.keys(errors).length > 0) {
76
- throw new EdgeValidationError("Invalid transfer options", errors);
201
+ throw new EdgeValidationError2("Invalid transfer options", errors);
77
202
  }
78
203
  }
79
204
 
@@ -105,6 +230,9 @@ var EdgeUserClient = class {
105
230
  }
106
231
  return this.server._apiRequest("POST", "/transfer", this.accessToken, body);
107
232
  }
233
+ async startTransferVerification(options) {
234
+ return startTransferVerification(this, options);
235
+ }
108
236
  async getTransfer(transferId) {
109
237
  validateTransferId(transferId);
110
238
  return this.server._apiRequest("GET", `/transfer/${encodeURIComponent(transferId)}`, this.accessToken);
@@ -225,16 +353,46 @@ function fromBase64Url(value) {
225
353
  }
226
354
 
227
355
  // src/mtls.ts
356
+ import fs from "fs";
228
357
  import https from "https";
358
+ import tls from "tls";
359
+ var CERT_PATTERN = /-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g;
360
+ function normalizeCaBundle(ca) {
361
+ if (!ca) return [];
362
+ const values = Array.isArray(ca) ? ca : [ca];
363
+ return values.flatMap((value) => {
364
+ const normalized = value.replace(/\\n/g, "\n").trim();
365
+ if (!normalized) return [];
366
+ const certificates = normalized.match(CERT_PATTERN);
367
+ return certificates ? certificates.map((certificate) => certificate.trim()) : [normalized];
368
+ });
369
+ }
370
+ function getNodeExtraCaCerts() {
371
+ const extraCaPath = process.env.NODE_EXTRA_CA_CERTS?.trim();
372
+ if (!extraCaPath) return [];
373
+ try {
374
+ return normalizeCaBundle(fs.readFileSync(extraCaPath, "utf8"));
375
+ } catch (error) {
376
+ const message = error instanceof Error ? error.message : String(error);
377
+ throw new Error(`EdgeConnectServer: failed to read NODE_EXTRA_CA_CERTS from "${extraCaPath}": ${message}`);
378
+ }
379
+ }
380
+ function buildServerCaBundle(ca) {
381
+ const customCa = normalizeCaBundle(ca);
382
+ if (customCa.length === 0) return void 0;
383
+ return Array.from(/* @__PURE__ */ new Set([...tls.rootCertificates, ...getNodeExtraCaCerts(), ...customCa]));
384
+ }
229
385
  function createHttpsAgent(config) {
386
+ const ca = buildServerCaBundle(config.ca);
230
387
  return new https.Agent({
231
388
  cert: config.cert,
232
389
  key: config.key,
233
390
  rejectUnauthorized: true,
234
- ...config.ca ? { ca: config.ca } : {}
391
+ ...ca ? { ca } : {}
235
392
  });
236
393
  }
237
394
  function createUndiciDispatcher(config) {
395
+ const ca = buildServerCaBundle(config.ca);
238
396
  const moduleName = "undici";
239
397
  const { Agent } = __require(moduleName);
240
398
  return new Agent({
@@ -242,7 +400,7 @@ function createUndiciDispatcher(config) {
242
400
  cert: config.cert,
243
401
  key: config.key,
244
402
  rejectUnauthorized: true,
245
- ...config.ca ? { ca: config.ca } : {}
403
+ ...ca ? { ca } : {}
246
404
  }
247
405
  });
248
406
  }
@@ -255,6 +413,112 @@ function validateMtlsConfig(config) {
255
413
  }
256
414
  }
257
415
 
416
+ // src/session.ts
417
+ import { EdgeAuthenticationError, EdgeValidationError as EdgeValidationError3 } from "@edge-markets/connect";
418
+ var EdgeUserSession = class {
419
+ constructor(options) {
420
+ if (!options.edge?.forUser || !options.edge?.refreshTokens) {
421
+ throw new EdgeValidationError3("EdgeUserSession requires an EdgeConnectServer instance", {
422
+ edge: ["Provide edge.forUser and edge.refreshTokens"]
423
+ });
424
+ }
425
+ const subjectId = typeof options.subjectId === "string" ? options.subjectId.trim() : "";
426
+ if (!subjectId) {
427
+ throw new EdgeValidationError3("EdgeUserSession subjectId is required", {
428
+ subjectId: ["Required"]
429
+ });
430
+ }
431
+ if (!options.tokenStore?.load || !options.tokenStore?.save) {
432
+ throw new EdgeValidationError3("EdgeUserSession tokenStore is required", {
433
+ tokenStore: ["Provide load(subjectId) and save(subjectId, value, context)"]
434
+ });
435
+ }
436
+ if (options.refreshSkewMs !== void 0 && (!Number.isFinite(options.refreshSkewMs) || options.refreshSkewMs < 0)) {
437
+ throw new EdgeValidationError3("refreshSkewMs must be a non-negative number", {
438
+ refreshSkewMs: ["Must be greater than or equal to 0"]
439
+ });
440
+ }
441
+ this.edge = options.edge;
442
+ this.subjectId = subjectId;
443
+ this.tokenStore = options.tokenStore;
444
+ this.tokenVault = options.tokenVault;
445
+ this.refreshSkewMs = options.refreshSkewMs ?? 6e4;
446
+ this.now = options.now ?? Date.now;
447
+ this.onRefresh = options.onRefresh;
448
+ }
449
+ async getValidTokens(options = {}) {
450
+ const tokens = await this.loadTokens();
451
+ if (!options.forceRefresh && tokens.expiresAt > this.now() + this.refreshSkewMs) {
452
+ return tokens;
453
+ }
454
+ const refreshed = await this.edge.refreshTokens(tokens.refreshToken);
455
+ await this.saveTokens(refreshed, true);
456
+ await this.onRefresh?.(refreshed);
457
+ return refreshed;
458
+ }
459
+ async getClient(options = {}) {
460
+ const tokens = await this.getValidTokens(options);
461
+ return this.edge.forUser(tokens.accessToken);
462
+ }
463
+ async withClient(callback) {
464
+ const tokens = await this.getValidTokens();
465
+ return callback(this.edge.forUser(tokens.accessToken), tokens);
466
+ }
467
+ async saveTokens(tokens, refreshed = false) {
468
+ validateTokens(tokens);
469
+ const value = this.tokenVault ? this.tokenVault.encryptTokens(tokens) : tokens;
470
+ await this.tokenStore.save(this.subjectId, value, { tokens, refreshed });
471
+ }
472
+ async loadTokens() {
473
+ const value = await this.tokenStore.load(this.subjectId);
474
+ if (!value) {
475
+ throw new EdgeAuthenticationError("No EDGE tokens are stored for this subject", {
476
+ subjectId: this.subjectId
477
+ });
478
+ }
479
+ if (typeof value === "string") {
480
+ if (!this.tokenVault) {
481
+ throw new EdgeAuthenticationError("Encrypted EDGE token envelope loaded but no tokenVault was configured", {
482
+ subjectId: this.subjectId
483
+ });
484
+ }
485
+ const tokens = this.tokenVault.decryptTokens(value);
486
+ validateTokens(tokens);
487
+ return tokens;
488
+ }
489
+ validateTokens(value);
490
+ return value;
491
+ }
492
+ };
493
+ function createEdgeUserSession(options) {
494
+ return new EdgeUserSession(options);
495
+ }
496
+ function validateTokens(tokens) {
497
+ const errors = {};
498
+ if (!tokens || typeof tokens !== "object") {
499
+ errors.tokens = ["Expected token object"];
500
+ } else {
501
+ if (typeof tokens.accessToken !== "string" || !tokens.accessToken.trim()) {
502
+ errors.accessToken = ["Required"];
503
+ }
504
+ if (typeof tokens.refreshToken !== "string" || !tokens.refreshToken.trim()) {
505
+ errors.refreshToken = ["Required"];
506
+ }
507
+ if (typeof tokens.expiresAt !== "number" || !Number.isFinite(tokens.expiresAt) || tokens.expiresAt <= 0) {
508
+ errors.expiresAt = ["Must be a positive Unix timestamp in milliseconds"];
509
+ }
510
+ if (typeof tokens.expiresIn !== "number" || !Number.isFinite(tokens.expiresIn) || tokens.expiresIn <= 0) {
511
+ errors.expiresIn = ["Must be a positive number"];
512
+ }
513
+ if (typeof tokens.scope !== "string") {
514
+ errors.scope = ["Required"];
515
+ }
516
+ }
517
+ if (Object.keys(errors).length > 0) {
518
+ throw new EdgeValidationError3("Invalid EDGE token payload", errors);
519
+ }
520
+ }
521
+
258
522
  // src/types.ts
259
523
  var DEFAULT_TIMEOUT = 3e4;
260
524
  var USER_AGENT = "@edge-markets/connect-node/1.0.0";
@@ -265,12 +529,113 @@ var DEFAULT_RETRY_CONFIG = {
265
529
  baseDelayMs: 1e3
266
530
  };
267
531
 
532
+ // src/webhook-reconciler.ts
533
+ var DEFAULT_MAX_PAGES = 10;
534
+ var EdgeWebhookReconciler = class {
535
+ constructor(options) {
536
+ this.running = false;
537
+ if (!options.edge?.syncWebhookEvents) {
538
+ throw new Error("EdgeWebhookReconciler: edge.syncWebhookEvents is required");
539
+ }
540
+ if (!options.cursorStore?.load || !options.cursorStore?.save) {
541
+ throw new Error("EdgeWebhookReconciler: cursorStore.load and cursorStore.save are required");
542
+ }
543
+ if (!options.processEvent) {
544
+ throw new Error("EdgeWebhookReconciler: processEvent is required");
545
+ }
546
+ if (options.maxPages !== void 0 && (!Number.isInteger(options.maxPages) || options.maxPages <= 0)) {
547
+ throw new Error("EdgeWebhookReconciler: maxPages must be a positive integer");
548
+ }
549
+ this.options = options;
550
+ }
551
+ async run() {
552
+ if (this.running) {
553
+ return {
554
+ processed: 0,
555
+ pages: 0,
556
+ hasMore: false,
557
+ skipped: true,
558
+ reason: "already_running"
559
+ };
560
+ }
561
+ this.running = true;
562
+ try {
563
+ return await this.runInternal();
564
+ } finally {
565
+ this.running = false;
566
+ }
567
+ }
568
+ async runInternal() {
569
+ let cursor = normalizeCursor(await this.options.cursorStore.load());
570
+ let processed = 0;
571
+ let pages = 0;
572
+ let hasMore = false;
573
+ const maxPages = this.options.maxPages ?? DEFAULT_MAX_PAGES;
574
+ while (pages < maxPages) {
575
+ const result = await this.options.edge.syncWebhookEvents({
576
+ ...cursor ? { afterEventId: cursor } : {},
577
+ ...this.options.limit !== void 0 ? { limit: this.options.limit } : {}
578
+ });
579
+ pages++;
580
+ hasMore = result.hasMore;
581
+ if (result.events.length === 0) {
582
+ return {
583
+ processed,
584
+ pages,
585
+ ...cursor ? { cursor } : {},
586
+ hasMore,
587
+ skipped: false,
588
+ ...hasMore ? { reason: "empty_page_with_has_more" } : {}
589
+ };
590
+ }
591
+ for (const event of result.events) {
592
+ await this.options.processEvent(event);
593
+ cursor = event.id;
594
+ await this.options.cursorStore.save(cursor);
595
+ processed++;
596
+ }
597
+ if (!result.hasMore) {
598
+ return {
599
+ processed,
600
+ pages,
601
+ ...cursor ? { cursor } : {},
602
+ hasMore: false,
603
+ skipped: false
604
+ };
605
+ }
606
+ }
607
+ return {
608
+ processed,
609
+ pages,
610
+ ...cursor ? { cursor } : {},
611
+ hasMore,
612
+ skipped: false,
613
+ ...hasMore ? { reason: "max_pages_reached" } : {}
614
+ };
615
+ }
616
+ };
617
+ function createWebhookReconciler(options) {
618
+ return new EdgeWebhookReconciler(options);
619
+ }
620
+ function normalizeCursor(cursor) {
621
+ if (typeof cursor !== "string") return void 0;
622
+ const trimmed = cursor.trim();
623
+ return trimmed || void 0;
624
+ }
625
+
268
626
  // src/edge-connect-server.ts
269
627
  var instances = /* @__PURE__ */ new Map();
270
628
  function getInstanceKey(config) {
271
- return `${config.clientId}:${config.environment}`;
629
+ return JSON.stringify([
630
+ config.clientId,
631
+ config.environment,
632
+ config.apiBaseUrl || "",
633
+ config.oauthBaseUrl || "",
634
+ config.partnerApiBaseUrl || "",
635
+ config.partnerClientId || ""
636
+ ]);
272
637
  }
273
- var MTLS_ENFORCED_HOSTS = /* @__PURE__ */ new Set(["connect-staging.edgeboost.io", "api.edgeboost.com"]);
638
+ var MTLS_ENFORCED_HOSTS = /* @__PURE__ */ new Set(["connect-staging.edgeboost.io", "connect.edgeboost.io"]);
274
639
  function isMtlsEnforcedHost(host) {
275
640
  if (MTLS_ENFORCED_HOSTS.has(host)) {
276
641
  return true;
@@ -292,6 +657,29 @@ function environmentRequiresMtls(environment, apiBaseUrl) {
292
657
  return false;
293
658
  }
294
659
  }
660
+ var CONNECT_API_BASE_PATH = "/connect/v1";
661
+ function stripTrailingSlash(value) {
662
+ let end = value.length;
663
+ while (end > 0 && value.charCodeAt(end - 1) === 47) {
664
+ end--;
665
+ }
666
+ return value.slice(0, end);
667
+ }
668
+ function derivePartnerApiBaseUrl(apiBaseUrl) {
669
+ const url = new URL(apiBaseUrl);
670
+ const normalizedPath = stripTrailingSlash(url.pathname);
671
+ if (normalizedPath.endsWith(CONNECT_API_BASE_PATH)) {
672
+ const prefix = normalizedPath.slice(0, -CONNECT_API_BASE_PATH.length);
673
+ url.pathname = `${prefix}/v1`;
674
+ } else {
675
+ url.pathname = normalizedPath || "/";
676
+ }
677
+ url.search = "";
678
+ url.hash = "";
679
+ return stripTrailingSlash(url.toString());
680
+ }
681
+ var SYNC_WEBHOOK_EVENTS_MIN_LIMIT = 1;
682
+ var SYNC_WEBHOOK_EVENTS_MAX_LIMIT = 100;
295
683
  var EdgeConnectServer = class _EdgeConnectServer {
296
684
  constructor(config) {
297
685
  this.partnerTokenCache = null;
@@ -307,6 +695,7 @@ var EdgeConnectServer = class _EdgeConnectServer {
307
695
  this.config = config;
308
696
  const envConfig = getEnvironmentConfig(config.environment);
309
697
  this.apiBaseUrl = config.apiBaseUrl || envConfig.apiBaseUrl;
698
+ this.partnerApiBaseUrl = stripTrailingSlash(config.partnerApiBaseUrl || derivePartnerApiBaseUrl(this.apiBaseUrl));
310
699
  this.oauthBaseUrl = config.oauthBaseUrl || envConfig.oauthBaseUrl;
311
700
  this.timeout = config.timeout || DEFAULT_TIMEOUT;
312
701
  this.retryConfig = {
@@ -349,10 +738,13 @@ var EdgeConnectServer = class _EdgeConnectServer {
349
738
  */
350
739
  forUser(accessToken) {
351
740
  if (!accessToken) {
352
- throw new EdgeAuthenticationError("accessToken is required when calling forUser()");
741
+ throw new EdgeAuthenticationError2("accessToken is required when calling forUser()");
353
742
  }
354
743
  return new EdgeUserClient(this, accessToken);
355
744
  }
745
+ createUserSession(options) {
746
+ return new EdgeUserSession({ ...options, edge: this });
747
+ }
356
748
  async exchangeCode(code, codeVerifier, redirectUri) {
357
749
  const tokenUrl = `${this.oauthBaseUrl}/token`;
358
750
  const body = {
@@ -410,7 +802,7 @@ var EdgeConnectServer = class _EdgeConnectServer {
410
802
  );
411
803
  if (!response.ok) {
412
804
  const error = await response.json().catch(() => ({}));
413
- throw new EdgeAuthenticationError(
805
+ throw new EdgeAuthenticationError2(
414
806
  error.message || error.error_description || "Token refresh failed. User may need to reconnect.",
415
807
  { tokenError: error }
416
808
  );
@@ -543,21 +935,41 @@ var EdgeConnectServer = class _EdgeConnectServer {
543
935
  */
544
936
  async syncWebhookEvents(options = {}) {
545
937
  if (!this.config.partnerClientId) {
546
- throw new EdgeAuthenticationError("syncWebhookEvents requires `partnerClientId` in EdgeConnectServer config");
938
+ throw new EdgeAuthenticationError2("syncWebhookEvents requires `partnerClientId` in EdgeConnectServer config");
547
939
  }
548
940
  if (!this.config.partnerClientSecret) {
549
- throw new EdgeAuthenticationError("syncWebhookEvents requires `partnerClientSecret` in EdgeConnectServer config");
941
+ throw new EdgeAuthenticationError2("syncWebhookEvents requires `partnerClientSecret` in EdgeConnectServer config");
550
942
  }
551
- const url = this.buildSyncUrl(options);
943
+ const normalizedOptions = this.normalizeSyncWebhookEventsOptions(options);
944
+ const url = this.buildSyncUrl(normalizedOptions);
552
945
  let token = await this.getPartnerToken();
553
- let response = await this.partnerFetch(url, token);
946
+ let { response, durationMs, attempt } = await this.partnerFetch(url, token);
554
947
  if (response.status === 401) {
948
+ await this.drainResponseBody(response);
949
+ this.config.onResponse?.({
950
+ status: response.status,
951
+ body: { error: "unauthorized" },
952
+ durationMs,
953
+ method: "GET",
954
+ url,
955
+ endpoint: "partner_webhook_sync",
956
+ attempt
957
+ });
555
958
  this.partnerTokenCache = null;
556
959
  token = await this.getPartnerToken();
557
- response = await this.partnerFetch(url, token);
960
+ ({ response, durationMs, attempt } = await this.partnerFetch(url, token));
558
961
  if (response.status === 401) {
559
962
  const body = await response.json().catch(() => ({}));
560
- throw new EdgeAuthenticationError(
963
+ this.config.onResponse?.({
964
+ status: response.status,
965
+ body: this.redactPartnerSyncResponseBody(body),
966
+ durationMs,
967
+ method: "GET",
968
+ url,
969
+ endpoint: "partner_webhook_sync",
970
+ attempt
971
+ });
972
+ throw new EdgeAuthenticationError2(
561
973
  body.message || "Partner token rejected after refresh \u2014 check partnerClientId/Secret",
562
974
  body
563
975
  );
@@ -565,6 +977,15 @@ var EdgeConnectServer = class _EdgeConnectServer {
565
977
  }
566
978
  if (!response.ok) {
567
979
  const body = await response.json().catch(() => ({}));
980
+ this.config.onResponse?.({
981
+ status: response.status,
982
+ body: this.redactPartnerSyncResponseBody(body),
983
+ durationMs,
984
+ method: "GET",
985
+ url,
986
+ endpoint: "partner_webhook_sync",
987
+ attempt
988
+ });
568
989
  throw new EdgeApiError(
569
990
  body.error || "sync_failed",
570
991
  body.message || `syncWebhookEvents failed with status ${response.status}`,
@@ -573,17 +994,57 @@ var EdgeConnectServer = class _EdgeConnectServer {
573
994
  );
574
995
  }
575
996
  const wire = await response.json().catch(() => ({}));
997
+ this.config.onResponse?.({
998
+ status: response.status,
999
+ body: {
1000
+ eventCount: wire.events?.length ?? 0,
1001
+ hasMore: !!wire.has_more
1002
+ },
1003
+ durationMs,
1004
+ method: "GET",
1005
+ url,
1006
+ endpoint: "partner_webhook_sync",
1007
+ attempt
1008
+ });
576
1009
  const events = (wire.events ?? []).map((entry) => entry.payload).filter((p) => !!p);
577
1010
  return {
578
1011
  events,
579
1012
  hasMore: !!wire.has_more
580
1013
  };
581
1014
  }
1015
+ /**
1016
+ * Creates a cursor-based webhook reconciler bound to this SDK instance.
1017
+ *
1018
+ * Use this from cron jobs to process events returned by
1019
+ * {@link EdgeConnectServer.syncWebhookEvents} without hand-writing
1020
+ * pagination, cursor advancement, or process-level concurrency guards.
1021
+ */
1022
+ createWebhookReconciler(options) {
1023
+ return new EdgeWebhookReconciler({ ...options, edge: this });
1024
+ }
582
1025
  /**
583
1026
  * Builds the sync URL with normalized query parameters.
584
1027
  */
1028
+ normalizeSyncWebhookEventsOptions(options) {
1029
+ const normalized = {};
1030
+ if (typeof options.afterEventId === "string" && options.afterEventId.trim()) {
1031
+ normalized.afterEventId = options.afterEventId.trim();
1032
+ }
1033
+ if (options.limit !== void 0) {
1034
+ if (!Number.isInteger(options.limit) || options.limit < SYNC_WEBHOOK_EVENTS_MIN_LIMIT || options.limit > SYNC_WEBHOOK_EVENTS_MAX_LIMIT) {
1035
+ throw new EdgeApiError(
1036
+ "invalid_sync_options",
1037
+ `syncWebhookEvents limit must be an integer between ${SYNC_WEBHOOK_EVENTS_MIN_LIMIT} and ${SYNC_WEBHOOK_EVENTS_MAX_LIMIT}`,
1038
+ 400,
1039
+ { limit: options.limit }
1040
+ );
1041
+ }
1042
+ normalized.limit = options.limit;
1043
+ }
1044
+ return normalized;
1045
+ }
585
1046
  buildSyncUrl(options) {
586
- const url = new URL(`${this.apiBaseUrl}/partner/webhooks/events/sync`);
1047
+ const url = new URL(`${this.partnerApiBaseUrl}/partner/webhooks/events/sync`);
587
1048
  if (options.afterEventId) {
588
1049
  url.searchParams.set("after_event_id", options.afterEventId);
589
1050
  }
@@ -598,22 +1059,57 @@ var EdgeConnectServer = class _EdgeConnectServer {
598
1059
  * lifecycle (process-wide, client_credentials) than user tokens.
599
1060
  */
600
1061
  async partnerFetch(url, token) {
601
- try {
602
- return await this.fetchWithTimeout(
1062
+ let lastError = null;
1063
+ for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
1064
+ if (attempt > 0) {
1065
+ await this.sleep(this.getRetryDelay(attempt - 1));
1066
+ }
1067
+ const startTime = Date.now();
1068
+ this.config.onRequest?.({
1069
+ method: "GET",
603
1070
  url,
604
- {
605
- method: "GET",
606
- headers: {
607
- Authorization: `Bearer ${token}`,
608
- "Content-Type": "application/json",
609
- "User-Agent": USER_AGENT
610
- }
611
- },
612
- this.dispatcher
613
- );
614
- } catch (error) {
615
- throw new EdgeNetworkError("syncWebhookEvents network failure", error);
1071
+ endpoint: "partner_webhook_sync",
1072
+ attempt
1073
+ });
1074
+ try {
1075
+ const response = await this.fetchWithTimeout(
1076
+ url,
1077
+ {
1078
+ method: "GET",
1079
+ headers: {
1080
+ Authorization: `Bearer ${token}`,
1081
+ "Content-Type": "application/json",
1082
+ "User-Agent": USER_AGENT
1083
+ }
1084
+ },
1085
+ this.dispatcher
1086
+ );
1087
+ const durationMs = Date.now() - startTime;
1088
+ if (response.status !== 401 && this.retryConfig.retryOn.includes(response.status) && attempt < this.retryConfig.maxRetries) {
1089
+ await this.drainResponseBody(response);
1090
+ this.config.onResponse?.({
1091
+ status: response.status,
1092
+ body: { retrying: true },
1093
+ durationMs,
1094
+ method: "GET",
1095
+ url,
1096
+ endpoint: "partner_webhook_sync",
1097
+ attempt
1098
+ });
1099
+ continue;
1100
+ }
1101
+ return { response, durationMs, attempt };
1102
+ } catch (error) {
1103
+ lastError = error;
1104
+ if (attempt >= this.retryConfig.maxRetries) {
1105
+ throw new EdgeNetworkError("syncWebhookEvents network failure", lastError);
1106
+ }
1107
+ }
616
1108
  }
1109
+ throw new EdgeNetworkError(
1110
+ `syncWebhookEvents network failure after ${this.retryConfig.maxRetries} retries`,
1111
+ lastError
1112
+ );
617
1113
  }
618
1114
  /**
619
1115
  * Fetches (or returns the cached) partner token via the
@@ -629,6 +1125,17 @@ var EdgeConnectServer = class _EdgeConnectServer {
629
1125
  }
630
1126
  const tokenUrl = `${this.oauthBaseUrl}/token`;
631
1127
  let response;
1128
+ const startTime = Date.now();
1129
+ this.config.onRequest?.({
1130
+ method: "POST",
1131
+ url: tokenUrl,
1132
+ endpoint: "oauth_token",
1133
+ body: {
1134
+ grant_type: "client_credentials",
1135
+ client_id: this.config.partnerClientId,
1136
+ client_secret: "[REDACTED]"
1137
+ }
1138
+ });
632
1139
  try {
633
1140
  response = await this.fetchWithTimeout(
634
1141
  tokenUrl,
@@ -649,16 +1156,24 @@ var EdgeConnectServer = class _EdgeConnectServer {
649
1156
  } catch (error) {
650
1157
  throw new EdgeNetworkError("Partner token request network failure", error);
651
1158
  }
1159
+ const durationMs = Date.now() - startTime;
1160
+ const data = await response.json().catch(() => ({}));
1161
+ this.config.onResponse?.({
1162
+ status: response.status,
1163
+ body: this.redactPartnerTokenResponseBody(data),
1164
+ durationMs,
1165
+ method: "POST",
1166
+ url: tokenUrl,
1167
+ endpoint: "oauth_token"
1168
+ });
652
1169
  if (!response.ok) {
653
- const body = await response.json().catch(() => ({}));
654
- throw new EdgeAuthenticationError(
655
- body.message || body.error_description || `Partner token request failed with status ${response.status}`,
656
- body
1170
+ throw new EdgeAuthenticationError2(
1171
+ data.message || data.error_description || `Partner token request failed with status ${response.status}`,
1172
+ data
657
1173
  );
658
1174
  }
659
- const data = await response.json().catch(() => ({}));
660
1175
  if (!data.access_token || typeof data.expires_in !== "number") {
661
- throw new EdgeAuthenticationError("Partner token response missing access_token or expires_in");
1176
+ throw new EdgeAuthenticationError2("Partner token response missing access_token or expires_in");
662
1177
  }
663
1178
  this.partnerTokenCache = {
664
1179
  token: data.access_token,
@@ -673,6 +1188,26 @@ var EdgeConnectServer = class _EdgeConnectServer {
673
1188
  _resetPartnerTokenCache() {
674
1189
  this.partnerTokenCache = null;
675
1190
  }
1191
+ redactPartnerTokenResponseBody(body) {
1192
+ return {
1193
+ ...body,
1194
+ ...typeof body.access_token === "string" ? { access_token: "[REDACTED]" } : {},
1195
+ ...typeof body.refresh_token === "string" ? { refresh_token: "[REDACTED]" } : {},
1196
+ ...typeof body.id_token === "string" ? { id_token: "[REDACTED]" } : {}
1197
+ };
1198
+ }
1199
+ redactPartnerSyncResponseBody(body) {
1200
+ if (Array.isArray(body.events)) {
1201
+ return {
1202
+ ...body,
1203
+ events: `[REDACTED ${body.events.length} events]`
1204
+ };
1205
+ }
1206
+ return body;
1207
+ }
1208
+ async drainResponseBody(response) {
1209
+ await response.arrayBuffer().catch(() => void 0);
1210
+ }
676
1211
  getRetryDelay(attempt) {
677
1212
  const { backoff, baseDelayMs } = this.retryConfig;
678
1213
  if (backoff === "exponential") {
@@ -718,7 +1253,7 @@ var EdgeConnectServer = class _EdgeConnectServer {
718
1253
  });
719
1254
  }
720
1255
  if (errorCode === "invalid_client" || errorMessage?.includes("Invalid client")) {
721
- return new EdgeAuthenticationError("Invalid client credentials. Check your client ID and secret.", {
1256
+ return new EdgeAuthenticationError2("Invalid client credentials. Check your client ID and secret.", {
722
1257
  edgeBoostError: error
723
1258
  });
724
1259
  }
@@ -732,7 +1267,7 @@ var EdgeConnectServer = class _EdgeConnectServer {
732
1267
  }
733
1268
  async handleApiErrorFromBody(error, status, path) {
734
1269
  if (status === 401) {
735
- return new EdgeAuthenticationError(error.message || "Access token is invalid or expired", error);
1270
+ return new EdgeAuthenticationError2(error.message || "Access token is invalid or expired", error);
736
1271
  }
737
1272
  if (status === 403) {
738
1273
  if (error.error === "consent_required") {
@@ -782,8 +1317,519 @@ var EdgeConnectServer = class _EdgeConnectServer {
782
1317
  }
783
1318
  };
784
1319
 
1320
+ // src/env.ts
1321
+ import { EDGE_ENVIRONMENTS, EdgeValidationError as EdgeValidationError4, getEnvironmentConfig as getEnvironmentConfig2 } from "@edge-markets/connect";
1322
+ import fs2 from "fs";
1323
+ var ENVIRONMENT_NAMES = new Set(Object.keys(EDGE_ENVIRONMENTS));
1324
+ function readEnv(env, name) {
1325
+ const value = env[name];
1326
+ if (typeof value !== "string") return void 0;
1327
+ const trimmed = value.trim();
1328
+ return trimmed ? trimmed : void 0;
1329
+ }
1330
+ function readFirstEnv(env, names) {
1331
+ for (const name of names) {
1332
+ const value = readEnv(env, name);
1333
+ if (value) return value;
1334
+ }
1335
+ return void 0;
1336
+ }
1337
+ function normalizePem(value) {
1338
+ return value.replace(/\\n/g, "\n").trim();
1339
+ }
1340
+ function readPemValue(env, valueName, pathName, warnings) {
1341
+ const inlineValue = readEnv(env, valueName);
1342
+ const filePath = readEnv(env, pathName);
1343
+ if (inlineValue && filePath) {
1344
+ warnings.push(`${valueName} and ${pathName} are both set; using ${valueName}`);
1345
+ }
1346
+ if (inlineValue) {
1347
+ return normalizePem(inlineValue);
1348
+ }
1349
+ if (!filePath) {
1350
+ return void 0;
1351
+ }
1352
+ try {
1353
+ return normalizePem(fs2.readFileSync(filePath, "utf8"));
1354
+ } catch (error) {
1355
+ const message = error instanceof Error ? error.message : String(error);
1356
+ throw new EdgeValidationError4(`Failed to read ${pathName}`, {
1357
+ [pathName]: [`Unable to read PEM file "${filePath}": ${message}`]
1358
+ });
1359
+ }
1360
+ }
1361
+ function readEnvironment(env) {
1362
+ const value = readEnv(env, "EDGE_ENVIRONMENT") || "staging";
1363
+ if (!ENVIRONMENT_NAMES.has(value)) {
1364
+ throw new EdgeValidationError4("Invalid EDGE_ENVIRONMENT", {
1365
+ EDGE_ENVIRONMENT: [`Must be one of: ${Array.from(ENVIRONMENT_NAMES).join(", ")}`]
1366
+ });
1367
+ }
1368
+ return value;
1369
+ }
1370
+ function readTimeout(env, override) {
1371
+ if (override !== void 0) return override;
1372
+ const value = readFirstEnv(env, ["EDGE_TIMEOUT_MS", "EDGE_REQUEST_TIMEOUT_MS"]);
1373
+ if (!value) return void 0;
1374
+ const timeout = Number(value);
1375
+ if (!Number.isInteger(timeout) || timeout <= 0) {
1376
+ throw new EdgeValidationError4("Invalid EDGE_TIMEOUT_MS", {
1377
+ EDGE_TIMEOUT_MS: ["Must be a positive integer number of milliseconds"]
1378
+ });
1379
+ }
1380
+ return timeout;
1381
+ }
1382
+ function buildMtlsConfig(env, requireMtls, warnings) {
1383
+ const cert = readPemValue(env, "EDGE_MTLS_CERT", "EDGE_MTLS_CERT_PATH", warnings);
1384
+ const key = readPemValue(env, "EDGE_MTLS_KEY", "EDGE_MTLS_KEY_PATH", warnings);
1385
+ const ca = readPemValue(env, "EDGE_MTLS_CA", "EDGE_MTLS_CA_PATH", warnings);
1386
+ if (!cert && !key && !ca) {
1387
+ if (requireMtls) {
1388
+ throw new EdgeValidationError4("mTLS is required but no EDGE_MTLS_CERT or EDGE_MTLS_KEY was provided", {
1389
+ EDGE_MTLS_CERT: ["Required when requireMtls is true"],
1390
+ EDGE_MTLS_KEY: ["Required when requireMtls is true"]
1391
+ });
1392
+ }
1393
+ return void 0;
1394
+ }
1395
+ if (!cert || !key) {
1396
+ throw new EdgeValidationError4("Incomplete mTLS configuration", {
1397
+ EDGE_MTLS_CERT: cert ? [] : ["Required when mTLS is configured"],
1398
+ EDGE_MTLS_KEY: key ? [] : ["Required when mTLS is configured"]
1399
+ });
1400
+ }
1401
+ return {
1402
+ enabled: true,
1403
+ cert,
1404
+ key,
1405
+ ...ca ? { ca } : {}
1406
+ };
1407
+ }
1408
+ function inferMtlsRequirement(environment, apiBaseUrl) {
1409
+ if (environment === "staging" || environment === "production") return true;
1410
+ const resolvedApiBaseUrl = apiBaseUrl || getEnvironmentConfig2(environment).apiBaseUrl;
1411
+ try {
1412
+ const host = new URL(resolvedApiBaseUrl).host;
1413
+ return host === "connect-staging.edgeboost.io" || host === "connect.edgeboost.io";
1414
+ } catch {
1415
+ return false;
1416
+ }
1417
+ }
1418
+ function createEdgeConnectServerFromEnv(options = {}) {
1419
+ const env = options.env ?? process.env;
1420
+ const warnings = [];
1421
+ const environment = readEnvironment(env);
1422
+ const apiBaseUrl = readEnv(env, "EDGE_API_BASE_URL");
1423
+ const mtlsRequiredByTarget = inferMtlsRequirement(environment, apiBaseUrl);
1424
+ const requireMtls = options.requireMtls ?? false;
1425
+ const mtls = buildMtlsConfig(env, requireMtls, warnings);
1426
+ const timeout = readTimeout(env, options.timeout);
1427
+ const clientId = readFirstEnv(env, ["EDGE_CLIENT_ID", "EDGE_CONNECT_CLIENT_ID"]) || "";
1428
+ const clientSecret = readFirstEnv(env, ["EDGE_CLIENT_SECRET", "EDGE_CONNECT_CLIENT_SECRET"]) || "";
1429
+ const oauthBaseUrl = readEnv(env, "EDGE_OAUTH_BASE_URL");
1430
+ const partnerApiBaseUrl = readEnv(env, "EDGE_PARTNER_API_BASE_URL");
1431
+ const partnerClientId = readFirstEnv(env, ["EDGE_PARTNER_CLIENT_ID", "EDGE_DASHBOARD_CLIENT_ID"]);
1432
+ const partnerClientSecret = readFirstEnv(env, ["EDGE_PARTNER_CLIENT_SECRET", "EDGE_DASHBOARD_CLIENT_SECRET"]);
1433
+ const validationErrors = {};
1434
+ if (!clientId) validationErrors.EDGE_CLIENT_ID = ["Required"];
1435
+ if (!clientSecret) validationErrors.EDGE_CLIENT_SECRET = ["Required"];
1436
+ if (Object.keys(validationErrors).length > 0) {
1437
+ throw new EdgeValidationError4("Missing required EDGE Connect environment variables", validationErrors);
1438
+ }
1439
+ if (mtlsRequiredByTarget && !mtls) {
1440
+ warnings.push(
1441
+ `EDGE_ENVIRONMENT=${environment} uses an mTLS-enforced EDGE Connect target; configure EDGE_MTLS_CERT and EDGE_MTLS_KEY before making API calls.`
1442
+ );
1443
+ }
1444
+ const config = {
1445
+ clientId,
1446
+ clientSecret,
1447
+ environment,
1448
+ ...apiBaseUrl ? { apiBaseUrl } : {},
1449
+ ...oauthBaseUrl ? { oauthBaseUrl } : {},
1450
+ ...partnerApiBaseUrl ? { partnerApiBaseUrl } : {},
1451
+ ...partnerClientId ? { partnerClientId } : {},
1452
+ ...partnerClientSecret ? { partnerClientSecret } : {},
1453
+ ...timeout ? { timeout } : {},
1454
+ ...options.retry ? { retry: options.retry } : {},
1455
+ ...options.onRequest ? { onRequest: options.onRequest } : {},
1456
+ ...options.onResponse ? { onResponse: options.onResponse } : {},
1457
+ ...options.mle ? { mle: options.mle } : {},
1458
+ ...mtls ? { mtls } : {}
1459
+ };
1460
+ const server = new EdgeConnectServer(config);
1461
+ return {
1462
+ server,
1463
+ config,
1464
+ capabilities: {
1465
+ mtlsEnabled: !!config.mtls?.enabled,
1466
+ webhookSyncConfigured: !!config.partnerClientId && !!config.partnerClientSecret,
1467
+ customApiBaseUrl: !!config.apiBaseUrl,
1468
+ customOAuthBaseUrl: !!config.oauthBaseUrl,
1469
+ customPartnerApiBaseUrl: !!config.partnerApiBaseUrl
1470
+ },
1471
+ warnings
1472
+ };
1473
+ }
1474
+
1475
+ // src/error-adapter.ts
1476
+ import {
1477
+ EdgeConsentRequiredError as EdgeConsentRequiredError2,
1478
+ EdgeError as EdgeError2,
1479
+ EdgeIdentityVerificationError as EdgeIdentityVerificationError2,
1480
+ EdgeNetworkError as EdgeNetworkError2,
1481
+ EdgeValidationError as EdgeValidationError5
1482
+ } from "@edge-markets/connect";
1483
+ function toEdgeHttpError(error) {
1484
+ const body = toEdgeProblemJson(error);
1485
+ return { status: body.status, body };
1486
+ }
1487
+ function toEdgeProblemJson(error) {
1488
+ if (error instanceof EdgeConsentRequiredError2) {
1489
+ return problem(error, "Consent required", error.statusCode ?? 403, {
1490
+ clientId: error.clientId,
1491
+ consentUrl: error.consentUrl
1492
+ });
1493
+ }
1494
+ if (error instanceof EdgeValidationError5) {
1495
+ return problem(error, "Validation failed", error.statusCode ?? 400, {
1496
+ validationErrors: error.validationErrors
1497
+ });
1498
+ }
1499
+ if (error instanceof EdgeIdentityVerificationError2) {
1500
+ return problem(error, "Identity verification failed", error.statusCode ?? 422, {
1501
+ fieldErrors: error.fieldErrors
1502
+ });
1503
+ }
1504
+ if (error instanceof EdgeNetworkError2) {
1505
+ return problem(error, "EDGE network error", error.statusCode ?? 502);
1506
+ }
1507
+ if (error instanceof EdgeError2) {
1508
+ return problem(error, edgeTitle(error), error.statusCode ?? 500, error.details);
1509
+ }
1510
+ const message = error instanceof Error ? error.message : "Unexpected error";
1511
+ return {
1512
+ type: "https://developer.edgemarkets.io/errors/internal_error",
1513
+ title: "Internal error",
1514
+ status: 500,
1515
+ detail: message,
1516
+ code: "internal_error"
1517
+ };
1518
+ }
1519
+ function problem(error, title, status, details) {
1520
+ return {
1521
+ type: `https://developer.edgemarkets.io/errors/${error.code}`,
1522
+ title,
1523
+ status,
1524
+ detail: error.message,
1525
+ code: error.code,
1526
+ ...details && Object.keys(details).length > 0 ? { details } : {}
1527
+ };
1528
+ }
1529
+ function edgeTitle(error) {
1530
+ return error.code.split("_").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
1531
+ }
1532
+
1533
+ // src/identity.ts
1534
+ import { EdgeValidationError as EdgeValidationError6 } from "@edge-markets/connect";
1535
+ var DEFAULT_IDENTITY_POLICY = {
1536
+ minNameScore: 70,
1537
+ minAddressScore: 65,
1538
+ requireZipMatch: true,
1539
+ requireApiVerified: true
1540
+ };
1541
+ function buildVerifyIdentityPayload(profile) {
1542
+ const firstName = firstNonEmpty(profile.firstName, profile.givenName);
1543
+ const lastName = firstNonEmpty(profile.lastName, profile.familyName);
1544
+ const address = profile.address ?? {};
1545
+ const payload = {
1546
+ firstName,
1547
+ lastName,
1548
+ address: {
1549
+ street: firstNonEmpty(address.street, address.line1, address.address1),
1550
+ city: firstNonEmpty(address.city),
1551
+ state: firstNonEmpty(address.state),
1552
+ zip: firstNonEmpty(address.zip, address.postalCode)
1553
+ }
1554
+ };
1555
+ const errors = {};
1556
+ if (!payload.firstName) errors.firstName = ["Required"];
1557
+ if (!payload.lastName) errors.lastName = ["Required"];
1558
+ if (!Object.values(payload.address).some((value) => !!value)) {
1559
+ errors.address = ["At least one address field is required"];
1560
+ }
1561
+ if (Object.keys(errors).length > 0) {
1562
+ throw new EdgeValidationError6("Partner identity profile is incomplete", errors);
1563
+ }
1564
+ return payload;
1565
+ }
1566
+ function evaluateIdentityResult(result, policy = {}) {
1567
+ const resolved = { ...DEFAULT_IDENTITY_POLICY, ...policy };
1568
+ validatePolicy(resolved);
1569
+ const reasons = [];
1570
+ const verifiedFlag = result.verified;
1571
+ if (resolved.requireApiVerified && verifiedFlag !== true) {
1572
+ reasons.push("api_not_verified");
1573
+ }
1574
+ const scores = result.scores;
1575
+ if (!scores) {
1576
+ reasons.push("scores_missing");
1577
+ } else {
1578
+ if (typeof scores.name === "number" && scores.name < resolved.minNameScore) {
1579
+ reasons.push("name_score_below_threshold");
1580
+ }
1581
+ if (typeof scores.address === "number" && scores.address < resolved.minAddressScore) {
1582
+ reasons.push("address_score_below_threshold");
1583
+ }
1584
+ if (resolved.requireZipMatch && scores.zipMatch !== true) {
1585
+ reasons.push("zip_mismatch");
1586
+ }
1587
+ }
1588
+ return {
1589
+ verified: reasons.length === 0,
1590
+ reasons,
1591
+ ...scores ? { scores } : {}
1592
+ };
1593
+ }
1594
+ function validatePolicy(policy) {
1595
+ const errors = {};
1596
+ if (!isScore(policy.minNameScore)) errors.minNameScore = ["Must be between 0 and 100"];
1597
+ if (!isScore(policy.minAddressScore)) errors.minAddressScore = ["Must be between 0 and 100"];
1598
+ if (Object.keys(errors).length > 0) {
1599
+ throw new EdgeValidationError6("Invalid identity policy", errors);
1600
+ }
1601
+ }
1602
+ function isScore(value) {
1603
+ return Number.isFinite(value) && value >= 0 && value <= 100;
1604
+ }
1605
+ function firstNonEmpty(...values) {
1606
+ for (const value of values) {
1607
+ if (typeof value === "string") {
1608
+ const trimmed = value.trim();
1609
+ if (trimmed) return trimmed;
1610
+ }
1611
+ }
1612
+ return "";
1613
+ }
1614
+
1615
+ // src/token-vault.ts
1616
+ import { EdgeValidationError as EdgeValidationError7 } from "@edge-markets/connect";
1617
+ import crypto2 from "crypto";
1618
+ var TOKEN_VAULT_VERSION = "edge_vault_v1";
1619
+ var IV_BYTES = 12;
1620
+ var TAG_BYTES = 16;
1621
+ var KEY_BYTES = 32;
1622
+ var EdgeTokenVault = class {
1623
+ constructor(options) {
1624
+ this.currentKey = normalizeVaultKey(options.currentKey, "currentKey");
1625
+ this.keysById = /* @__PURE__ */ new Map([[this.currentKey.id, this.currentKey.key]]);
1626
+ for (const [index, key] of (options.previousKeys ?? []).entries()) {
1627
+ const normalized = normalizeVaultKey(key, `previousKeys[${index}]`);
1628
+ if (this.keysById.has(normalized.id)) {
1629
+ throw new EdgeValidationError7("Duplicate token vault key ID", {
1630
+ keyId: [`Duplicate key ID "${normalized.id}"`]
1631
+ });
1632
+ }
1633
+ this.keysById.set(normalized.id, normalized.key);
1634
+ }
1635
+ }
1636
+ encryptTokens(tokens) {
1637
+ validateEdgeTokens(tokens);
1638
+ const iv = crypto2.randomBytes(IV_BYTES);
1639
+ const aad = Buffer.from(`${TOKEN_VAULT_VERSION}.${this.currentKey.id}`, "utf8");
1640
+ const cipher = crypto2.createCipheriv("aes-256-gcm", this.currentKey.key, iv);
1641
+ cipher.setAAD(aad);
1642
+ const plaintext = Buffer.from(JSON.stringify(tokens), "utf8");
1643
+ const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
1644
+ const tag = cipher.getAuthTag();
1645
+ return [
1646
+ TOKEN_VAULT_VERSION,
1647
+ toBase64Url2(Buffer.from(this.currentKey.id, "utf8")),
1648
+ toBase64Url2(iv),
1649
+ toBase64Url2(tag),
1650
+ toBase64Url2(ciphertext)
1651
+ ].join(".");
1652
+ }
1653
+ decryptTokens(envelope) {
1654
+ const parsed = parseEnvelope(envelope);
1655
+ const key = this.keysById.get(parsed.keyId);
1656
+ if (!key) {
1657
+ throw new EdgeValidationError7("Unknown token vault key ID", {
1658
+ keyId: [`No key configured for "${parsed.keyId}"`]
1659
+ });
1660
+ }
1661
+ try {
1662
+ const decipher = crypto2.createDecipheriv("aes-256-gcm", key, parsed.iv);
1663
+ decipher.setAAD(Buffer.from(`${TOKEN_VAULT_VERSION}.${parsed.keyId}`, "utf8"));
1664
+ decipher.setAuthTag(parsed.tag);
1665
+ const plaintext = Buffer.concat([decipher.update(parsed.ciphertext), decipher.final()]).toString("utf8");
1666
+ const tokens = JSON.parse(plaintext);
1667
+ validateEdgeTokens(tokens);
1668
+ return tokens;
1669
+ } catch (error) {
1670
+ if (error instanceof EdgeValidationError7) throw error;
1671
+ throw new EdgeValidationError7("Failed to decrypt EDGE tokens", {
1672
+ tokenEnvelope: ["Envelope is malformed, corrupted, or encrypted with a different key"]
1673
+ });
1674
+ }
1675
+ }
1676
+ isEncrypted(value) {
1677
+ return typeof value === "string" && value.startsWith(`${TOKEN_VAULT_VERSION}.`);
1678
+ }
1679
+ };
1680
+ function createEdgeTokenVault(options) {
1681
+ return new EdgeTokenVault(options);
1682
+ }
1683
+ function isEdgeTokenVaultEnvelope(value) {
1684
+ return typeof value === "string" && value.startsWith(`${TOKEN_VAULT_VERSION}.`);
1685
+ }
1686
+ function normalizeVaultKey(input, fieldName) {
1687
+ const id = typeof input.id === "string" ? input.id.trim() : "";
1688
+ if (!id) {
1689
+ throw new EdgeValidationError7("Token vault key ID is required", {
1690
+ [`${fieldName}.id`]: ["Required"]
1691
+ });
1692
+ }
1693
+ return {
1694
+ id,
1695
+ key: normalizeKeyMaterial(input.key, `${fieldName}.key`)
1696
+ };
1697
+ }
1698
+ function normalizeKeyMaterial(input, fieldName) {
1699
+ const candidates = [];
1700
+ if (Buffer.isBuffer(input)) {
1701
+ candidates.push(input);
1702
+ } else if (input instanceof Uint8Array) {
1703
+ candidates.push(Buffer.from(input));
1704
+ } else if (typeof input === "string") {
1705
+ const value = input.trim();
1706
+ if (value.startsWith("base64:")) {
1707
+ candidates.push(Buffer.from(value.slice("base64:".length), "base64"));
1708
+ } else if (value.startsWith("base64url:")) {
1709
+ candidates.push(fromBase64Url2(value.slice("base64url:".length)));
1710
+ } else if (/^[0-9a-f]{64}$/i.test(value)) {
1711
+ candidates.push(Buffer.from(value, "hex"));
1712
+ } else {
1713
+ candidates.push(Buffer.from(value, "base64"));
1714
+ candidates.push(fromBase64Url2(value));
1715
+ candidates.push(Buffer.from(value, "utf8"));
1716
+ }
1717
+ }
1718
+ const key = candidates.find((candidate) => candidate.length === KEY_BYTES);
1719
+ if (!key) {
1720
+ throw new EdgeValidationError7("Token vault key must decode to 32 bytes", {
1721
+ [fieldName]: ["Provide a 32-byte key as base64, base64url, hex, Buffer, or Uint8Array"]
1722
+ });
1723
+ }
1724
+ return key;
1725
+ }
1726
+ function validateEdgeTokens(tokens) {
1727
+ const errors = {};
1728
+ if (!tokens || typeof tokens !== "object") {
1729
+ errors.tokens = ["Expected token object"];
1730
+ } else {
1731
+ if (typeof tokens.accessToken !== "string" || !tokens.accessToken) {
1732
+ errors.accessToken = ["Required"];
1733
+ }
1734
+ if (typeof tokens.refreshToken !== "string" || !tokens.refreshToken) {
1735
+ errors.refreshToken = ["Required"];
1736
+ }
1737
+ if (typeof tokens.expiresIn !== "number" || !Number.isFinite(tokens.expiresIn) || tokens.expiresIn <= 0) {
1738
+ errors.expiresIn = ["Must be a positive number"];
1739
+ }
1740
+ if (typeof tokens.expiresAt !== "number" || !Number.isFinite(tokens.expiresAt) || tokens.expiresAt <= 0) {
1741
+ errors.expiresAt = ["Must be a positive Unix timestamp in milliseconds"];
1742
+ }
1743
+ if (typeof tokens.scope !== "string") {
1744
+ errors.scope = ["Required"];
1745
+ }
1746
+ if (tokens.idToken !== void 0 && typeof tokens.idToken !== "string") {
1747
+ errors.idToken = ["Must be a string when provided"];
1748
+ }
1749
+ }
1750
+ if (Object.keys(errors).length > 0) {
1751
+ throw new EdgeValidationError7("Invalid EDGE token payload", errors);
1752
+ }
1753
+ }
1754
+ function parseEnvelope(envelope) {
1755
+ if (typeof envelope !== "string") {
1756
+ throw new EdgeValidationError7("Token envelope must be a string", {
1757
+ tokenEnvelope: ["Expected string"]
1758
+ });
1759
+ }
1760
+ const parts = envelope.split(".");
1761
+ if (parts.length !== 5 || parts[0] !== TOKEN_VAULT_VERSION) {
1762
+ throw new EdgeValidationError7("Invalid EDGE token vault envelope", {
1763
+ tokenEnvelope: [`Expected ${TOKEN_VAULT_VERSION}.<keyId>.<iv>.<tag>.<ciphertext>`]
1764
+ });
1765
+ }
1766
+ const [, encodedKeyId, encodedIv, encodedTag, encodedCiphertext] = parts;
1767
+ const keyId = fromBase64Url2(encodedKeyId).toString("utf8");
1768
+ const iv = fromBase64Url2(encodedIv);
1769
+ const tag = fromBase64Url2(encodedTag);
1770
+ const ciphertext = fromBase64Url2(encodedCiphertext);
1771
+ const errors = {};
1772
+ if (!keyId) errors.keyId = ["Required"];
1773
+ if (iv.length !== IV_BYTES) errors.iv = [`Must be ${IV_BYTES} bytes`];
1774
+ if (tag.length !== TAG_BYTES) errors.tag = [`Must be ${TAG_BYTES} bytes`];
1775
+ if (ciphertext.length === 0) errors.ciphertext = ["Required"];
1776
+ if (Object.keys(errors).length > 0) {
1777
+ throw new EdgeValidationError7("Invalid EDGE token vault envelope", errors);
1778
+ }
1779
+ return { keyId, iv, tag, ciphertext };
1780
+ }
1781
+ function toBase64Url2(value) {
1782
+ return value.toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
1783
+ }
1784
+ function fromBase64Url2(value) {
1785
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
1786
+ const padding = normalized.length % 4 === 0 ? "" : "=".repeat(4 - normalized.length % 4);
1787
+ return Buffer.from(normalized + padding, "base64");
1788
+ }
1789
+
1790
+ // src/webhook-guards.ts
1791
+ import { EdgeValidationError as EdgeValidationError8 } from "@edge-markets/connect";
1792
+ function isTransferWebhookEvent(event) {
1793
+ return event.type.startsWith("transfer.");
1794
+ }
1795
+ function assertTransferEventMatchesIntent(event, intent) {
1796
+ if (!isTransferWebhookEvent(event)) {
1797
+ throw new EdgeValidationError8("Webhook event is not a transfer event", {
1798
+ type: [`Received ${event.type}`]
1799
+ });
1800
+ }
1801
+ const data = event.data;
1802
+ const errors = {};
1803
+ if (data.transferId !== intent.transferId) {
1804
+ errors.transferId = [`Expected ${intent.transferId}, received ${data.transferId}`];
1805
+ }
1806
+ if (intent.type && data.type !== intent.type) {
1807
+ errors.type = [`Expected ${intent.type}, received ${data.type}`];
1808
+ }
1809
+ if (intent.status && data.status !== intent.status) {
1810
+ errors.status = [`Expected ${intent.status}, received ${data.status}`];
1811
+ }
1812
+ if (intent.amount !== void 0) {
1813
+ const expectedAmount = normalizeMoneyAmount(intent.amount);
1814
+ const receivedAmount = normalizeMoneyAmount(data.amount);
1815
+ if (receivedAmount !== expectedAmount) {
1816
+ errors.amount = [`Expected ${expectedAmount}, received ${receivedAmount}`];
1817
+ }
1818
+ }
1819
+ if (Object.keys(errors).length > 0) {
1820
+ throw new EdgeValidationError8("Webhook transfer event does not match expected transfer intent", errors);
1821
+ }
1822
+ }
1823
+
1824
+ // src/webhook-parser.ts
1825
+ import {
1826
+ EDGE_WEBHOOK_EVENT_TYPES,
1827
+ EdgeAuthenticationError as EdgeAuthenticationError3,
1828
+ EdgeValidationError as EdgeValidationError9
1829
+ } from "@edge-markets/connect";
1830
+
785
1831
  // src/webhook-signing.ts
786
- import crypto from "crypto";
1832
+ import crypto3 from "crypto";
787
1833
  function verifyWebhookSignature(header, body, secret, options = {}) {
788
1834
  if (typeof header !== "string" || typeof body !== "string" || typeof secret !== "string") {
789
1835
  return false;
@@ -800,28 +1846,180 @@ function verifyWebhookSignature(header, body, secret, options = {}) {
800
1846
  const now = Math.floor(Date.now() / 1e3);
801
1847
  if (Math.abs(now - timestamp) > toleranceSeconds) return false;
802
1848
  const signedContent = `${timestamp}.${body}`;
803
- const expectedHmac = crypto.createHmac("sha256", secret).update(signedContent).digest("hex");
1849
+ const expectedHmac = crypto3.createHmac("sha256", secret).update(signedContent).digest("hex");
804
1850
  try {
805
1851
  const sigBuf = Buffer.from(signature, "hex");
806
1852
  const expBuf = Buffer.from(expectedHmac, "hex");
807
1853
  if (sigBuf.length !== expBuf.length) return false;
808
- return crypto.timingSafeEqual(sigBuf, expBuf);
1854
+ return crypto3.timingSafeEqual(sigBuf, expBuf);
1855
+ } catch {
1856
+ return false;
1857
+ }
1858
+ }
1859
+
1860
+ // src/webhook-parser.ts
1861
+ var EVENT_TYPES = new Set(EDGE_WEBHOOK_EVENT_TYPES);
1862
+ var TRANSFER_TYPES = /* @__PURE__ */ new Set(["debit", "credit"]);
1863
+ function parseAndVerifyWebhook(options) {
1864
+ const body = normalizeRawBody(options.rawBody);
1865
+ const header = normalizeSignatureHeader(options.signatureHeader);
1866
+ const signatureTimestamp = extractWebhookSignatureTimestamp(header);
1867
+ if (!header || !signatureTimestamp) {
1868
+ throw new EdgeAuthenticationError3("Missing or malformed EDGE webhook signature");
1869
+ }
1870
+ if (!verifyWebhookSignature(header, body, options.secret, { toleranceSeconds: options.toleranceSeconds })) {
1871
+ throw new EdgeAuthenticationError3("Invalid EDGE webhook signature");
1872
+ }
1873
+ let parsed;
1874
+ try {
1875
+ parsed = JSON.parse(body);
1876
+ } catch {
1877
+ throw new EdgeValidationError9("EDGE webhook body must be valid JSON", {
1878
+ rawBody: ["Unable to parse JSON after signature verification"]
1879
+ });
1880
+ }
1881
+ const event = validateEdgeWebhookEvent(parsed);
1882
+ return {
1883
+ event,
1884
+ eventId: event.id,
1885
+ eventType: event.type,
1886
+ signatureTimestamp,
1887
+ receivedAt: options.receivedAt ?? /* @__PURE__ */ new Date()
1888
+ };
1889
+ }
1890
+ function extractWebhookSignatureTimestamp(header) {
1891
+ const normalizedHeader = normalizeSignatureHeader(header);
1892
+ if (!normalizedHeader) return void 0;
1893
+ const part = normalizedHeader.split(",").map((value) => value.trim()).find((value) => value.startsWith("t="));
1894
+ if (!part) return void 0;
1895
+ const timestamp = Number(part.slice(2));
1896
+ if (!Number.isInteger(timestamp) || timestamp <= 0) return void 0;
1897
+ return timestamp;
1898
+ }
1899
+ function isEdgeWebhookEvent(value) {
1900
+ try {
1901
+ validateEdgeWebhookEvent(value);
1902
+ return true;
809
1903
  } catch {
810
1904
  return false;
811
1905
  }
812
1906
  }
1907
+ function validateEdgeWebhookEvent(value) {
1908
+ const errors = {};
1909
+ if (!isRecord(value)) {
1910
+ throw new EdgeValidationError9("EDGE webhook event must be an object", {
1911
+ event: ["Expected JSON object"]
1912
+ });
1913
+ }
1914
+ const id = value.id;
1915
+ const type = value.type;
1916
+ const createdAt = value.created_at;
1917
+ const data = value.data;
1918
+ if (typeof id !== "string" || !id.trim()) {
1919
+ errors.id = ["Required string"];
1920
+ }
1921
+ if (typeof type !== "string" || !EVENT_TYPES.has(type)) {
1922
+ errors.type = [`Must be one of: ${EDGE_WEBHOOK_EVENT_TYPES.join(", ")}`];
1923
+ }
1924
+ if (typeof createdAt !== "string" || !createdAt.trim() || Number.isNaN(Date.parse(createdAt))) {
1925
+ errors.created_at = ["Required ISO 8601 timestamp string"];
1926
+ }
1927
+ if (!isRecord(data)) {
1928
+ errors.data = ["Required object"];
1929
+ }
1930
+ if (Object.keys(errors).length === 0) {
1931
+ validateKnownEventData(type, data, errors);
1932
+ }
1933
+ if (Object.keys(errors).length > 0) {
1934
+ throw new EdgeValidationError9("Invalid EDGE webhook event", errors);
1935
+ }
1936
+ return value;
1937
+ }
1938
+ function validateKnownEventData(type, data, errors) {
1939
+ switch (type) {
1940
+ case "transfer.completed":
1941
+ validateTransferEventData(data, "completed", errors);
1942
+ return;
1943
+ case "transfer.failed":
1944
+ validateTransferEventData(data, "failed", errors);
1945
+ if (data.reason !== void 0 && typeof data.reason !== "string") {
1946
+ errors["data.reason"] = ["Must be a string when provided"];
1947
+ }
1948
+ return;
1949
+ case "transfer.expired":
1950
+ validateTransferEventData(data, "expired", errors);
1951
+ return;
1952
+ case "transfer.processing":
1953
+ validateTransferEventData(data, "processing", errors);
1954
+ return;
1955
+ case "consent.revoked":
1956
+ validateRequiredString(data, "userId", errors);
1957
+ validateRequiredString(data, "clientId", errors);
1958
+ validateRequiredIsoTimestamp(data, "revokedAt", errors);
1959
+ return;
1960
+ default: {
1961
+ const exhaustive = type;
1962
+ throw new EdgeValidationError9("Unsupported EDGE webhook event type", {
1963
+ type: [`Unsupported event type: ${exhaustive}`]
1964
+ });
1965
+ }
1966
+ }
1967
+ }
1968
+ function validateTransferEventData(data, status, errors) {
1969
+ validateRequiredString(data, "transferId", errors);
1970
+ if (data.status !== status) {
1971
+ errors["data.status"] = [`Must be "${status}" for this event type`];
1972
+ }
1973
+ if (typeof data.type !== "string" || !TRANSFER_TYPES.has(data.type)) {
1974
+ errors["data.type"] = ['Must be "debit" or "credit"'];
1975
+ }
1976
+ validateRequiredString(data, "amount", errors);
1977
+ }
1978
+ function validateRequiredString(data, field, errors) {
1979
+ if (typeof data[field] !== "string" || !data[field].trim()) {
1980
+ errors[`data.${field}`] = ["Required string"];
1981
+ }
1982
+ }
1983
+ function validateRequiredIsoTimestamp(data, field, errors) {
1984
+ if (typeof data[field] !== "string" || !data[field].trim() || Number.isNaN(Date.parse(data[field]))) {
1985
+ errors[`data.${field}`] = ["Required ISO 8601 timestamp string"];
1986
+ }
1987
+ }
1988
+ function normalizeRawBody(rawBody) {
1989
+ if (typeof rawBody === "string") {
1990
+ return rawBody;
1991
+ }
1992
+ if (Buffer.isBuffer(rawBody) || rawBody instanceof Uint8Array) {
1993
+ return Buffer.from(rawBody).toString("utf8");
1994
+ }
1995
+ throw new EdgeValidationError9("EDGE webhook raw body is required", {
1996
+ rawBody: ["Pass the raw UTF-8 request body, not a parsed JSON object"]
1997
+ });
1998
+ }
1999
+ function normalizeSignatureHeader(header) {
2000
+ if (Array.isArray(header)) {
2001
+ return header.map((value) => value.trim()).filter(Boolean).join(",");
2002
+ }
2003
+ if (typeof header !== "string") return void 0;
2004
+ const trimmed = header.trim();
2005
+ return trimmed || void 0;
2006
+ }
2007
+ function isRecord(value) {
2008
+ return !!value && typeof value === "object" && !Array.isArray(value);
2009
+ }
813
2010
 
814
2011
  // src/index.ts
815
2012
  import {
816
2013
  EdgeApiError as EdgeApiError2,
817
- EdgeAuthenticationError as EdgeAuthenticationError2,
818
- EdgeConsentRequiredError as EdgeConsentRequiredError2,
819
- EdgeError as EdgeError2,
820
- EdgeIdentityVerificationError as EdgeIdentityVerificationError2,
2014
+ EdgeAuthenticationError as EdgeAuthenticationError4,
2015
+ EdgeConsentRequiredError as EdgeConsentRequiredError3,
2016
+ EdgeError as EdgeError3,
2017
+ EdgeIdentityVerificationError as EdgeIdentityVerificationError3,
821
2018
  EdgeInsufficientScopeError as EdgeInsufficientScopeError2,
822
- EdgeNetworkError as EdgeNetworkError2,
2019
+ EdgeNetworkError as EdgeNetworkError3,
823
2020
  EdgeNotFoundError as EdgeNotFoundError2,
824
2021
  EdgeTokenExchangeError as EdgeTokenExchangeError2,
2022
+ EdgeValidationError as EdgeValidationError10,
825
2023
  isApiError,
826
2024
  isAuthenticationError,
827
2025
  isConsentRequiredError,
@@ -830,32 +2028,56 @@ import {
830
2028
  isNetworkError
831
2029
  } from "@edge-markets/connect";
832
2030
  import {
833
- EDGE_WEBHOOK_EVENT_TYPES,
2031
+ EDGE_WEBHOOK_EVENT_TYPES as EDGE_WEBHOOK_EVENT_TYPES2,
834
2032
  TRANSFER_CATEGORIES,
835
- getEnvironmentConfig as getEnvironmentConfig2,
2033
+ getEnvironmentConfig as getEnvironmentConfig3,
836
2034
  isProductionEnvironment
837
2035
  } from "@edge-markets/connect";
838
2036
  export {
839
- EDGE_WEBHOOK_EVENT_TYPES,
2037
+ EDGE_WEBHOOK_EVENT_TYPES2 as EDGE_WEBHOOK_EVENT_TYPES,
840
2038
  EdgeApiError2 as EdgeApiError,
841
- EdgeAuthenticationError2 as EdgeAuthenticationError,
2039
+ EdgeAuthenticationError4 as EdgeAuthenticationError,
842
2040
  EdgeConnectServer,
843
- EdgeConsentRequiredError2 as EdgeConsentRequiredError,
844
- EdgeError2 as EdgeError,
845
- EdgeIdentityVerificationError2 as EdgeIdentityVerificationError,
2041
+ EdgeConsentRequiredError3 as EdgeConsentRequiredError,
2042
+ EdgeError3 as EdgeError,
2043
+ EdgeIdentityVerificationError3 as EdgeIdentityVerificationError,
846
2044
  EdgeInsufficientScopeError2 as EdgeInsufficientScopeError,
847
- EdgeNetworkError2 as EdgeNetworkError,
2045
+ EdgeNetworkError3 as EdgeNetworkError,
848
2046
  EdgeNotFoundError2 as EdgeNotFoundError,
849
2047
  EdgeTokenExchangeError2 as EdgeTokenExchangeError,
2048
+ EdgeTokenVault,
850
2049
  EdgeUserClient,
2050
+ EdgeUserSession,
2051
+ EdgeValidationError10 as EdgeValidationError,
2052
+ EdgeWebhookReconciler,
851
2053
  TRANSFER_CATEGORIES,
852
- getEnvironmentConfig2 as getEnvironmentConfig,
2054
+ assertSameTransferIntent,
2055
+ assertTransferEventMatchesIntent,
2056
+ buildVerifyIdentityPayload,
2057
+ createEdgeConnectServerFromEnv,
2058
+ createEdgeTokenVault,
2059
+ createEdgeUserSession,
2060
+ createTransferIdempotencyKey,
2061
+ createWebhookReconciler,
2062
+ evaluateIdentityResult,
2063
+ extractWebhookSignatureTimestamp,
2064
+ getEnvironmentConfig3 as getEnvironmentConfig,
853
2065
  isApiError,
854
2066
  isAuthenticationError,
855
2067
  isConsentRequiredError,
856
2068
  isEdgeError,
2069
+ isEdgeTokenVaultEnvelope,
2070
+ isEdgeWebhookEvent,
857
2071
  isIdentityVerificationError,
858
2072
  isNetworkError,
859
2073
  isProductionEnvironment,
2074
+ isTransferWebhookEvent,
2075
+ normalizeMoneyAmount,
2076
+ parseAndVerifyWebhook,
2077
+ startTransferVerification,
2078
+ toEdgeHttpError,
2079
+ toEdgeProblemJson,
2080
+ validateEdgeWebhookEvent,
2081
+ validateTransferAmount,
860
2082
  verifyWebhookSignature
861
2083
  };