@edge-markets/connect-node 1.3.0 → 1.4.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.d.mts +34 -13
- package/dist/index.d.ts +34 -13
- package/dist/index.js +179 -132
- package/dist/index.mjs +160 -117
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EdgeEnvironment,
|
|
1
|
+
import { EdgeEnvironment, User, VerifyIdentityOptions, VerifyIdentityResult, Balance, Transfer, ListTransfersParams, TransferList, EdgeTokens } from '@edge-markets/connect';
|
|
2
2
|
export { Balance, EdgeApiError, EdgeAuthenticationError, EdgeConsentRequiredError, EdgeEnvironment, EdgeError, EdgeInsufficientScopeError, EdgeNetworkError, EdgeNotFoundError, EdgeTokenExchangeError, EdgeTokens, ListTransfersParams, Transfer, TransferList, TransferListItem, TransferStatus, TransferType, User, getEnvironmentConfig, isApiError, isAuthenticationError, isConsentRequiredError, isEdgeError, isNetworkError, isProductionEnvironment } from '@edge-markets/connect';
|
|
3
3
|
|
|
4
4
|
interface RequestInfo {
|
|
@@ -46,6 +46,29 @@ interface TransferOptions {
|
|
|
46
46
|
amount: string;
|
|
47
47
|
idempotencyKey: string;
|
|
48
48
|
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* A lightweight, user-scoped API client created via {@link EdgeConnectServer.forUser}.
|
|
52
|
+
*
|
|
53
|
+
* Each instance holds a single access token and delegates the actual HTTP work
|
|
54
|
+
* to its parent {@link EdgeConnectServer}.
|
|
55
|
+
*/
|
|
56
|
+
declare class EdgeUserClient {
|
|
57
|
+
private readonly server;
|
|
58
|
+
readonly accessToken: string;
|
|
59
|
+
constructor(server: EdgeConnectServer, accessToken: string);
|
|
60
|
+
getUser(): Promise<User>;
|
|
61
|
+
verifyIdentity(options: VerifyIdentityOptions): Promise<VerifyIdentityResult>;
|
|
62
|
+
getBalance(): Promise<Balance>;
|
|
63
|
+
initiateTransfer(options: TransferOptions): Promise<Transfer>;
|
|
64
|
+
verifyTransfer(transferId: string, otp: string): Promise<Transfer>;
|
|
65
|
+
getTransfer(transferId: string): Promise<Transfer>;
|
|
66
|
+
listTransfers(params?: ListTransfersParams): Promise<TransferList>;
|
|
67
|
+
revokeConsent(): Promise<{
|
|
68
|
+
revoked: boolean;
|
|
69
|
+
}>;
|
|
70
|
+
}
|
|
71
|
+
|
|
49
72
|
declare class EdgeConnectServer {
|
|
50
73
|
private readonly config;
|
|
51
74
|
private readonly apiBaseUrl;
|
|
@@ -55,25 +78,23 @@ declare class EdgeConnectServer {
|
|
|
55
78
|
static getInstance(config: EdgeConnectServerConfig): EdgeConnectServer;
|
|
56
79
|
static clearInstances(): void;
|
|
57
80
|
constructor(config: EdgeConnectServerConfig);
|
|
81
|
+
/**
|
|
82
|
+
* Create a user-scoped client for making authenticated API calls.
|
|
83
|
+
*
|
|
84
|
+
* The returned {@link EdgeUserClient} is lightweight — create one per request
|
|
85
|
+
* or per user session and discard it when the token changes.
|
|
86
|
+
*/
|
|
87
|
+
forUser(accessToken: string): EdgeUserClient;
|
|
58
88
|
exchangeCode(code: string, codeVerifier: string, redirectUri?: string): Promise<EdgeTokens>;
|
|
59
89
|
refreshTokens(refreshToken: string): Promise<EdgeTokens>;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
initiateTransfer(accessToken: string, options: TransferOptions): Promise<Transfer>;
|
|
63
|
-
private validateTransferOptions;
|
|
64
|
-
verifyTransfer(accessToken: string, transferId: string, otp: string): Promise<Transfer>;
|
|
65
|
-
getTransfer(accessToken: string, transferId: string): Promise<Transfer>;
|
|
66
|
-
listTransfers(accessToken: string, params?: ListTransfersParams): Promise<TransferList>;
|
|
67
|
-
revokeConsent(accessToken: string): Promise<{
|
|
68
|
-
revoked: boolean;
|
|
69
|
-
}>;
|
|
90
|
+
/** @internal Called by {@link EdgeUserClient} — not part of the public API. */
|
|
91
|
+
_apiRequest<T>(method: string, path: string, accessToken: string, body?: unknown): Promise<T>;
|
|
70
92
|
private getRetryDelay;
|
|
71
93
|
private sleep;
|
|
72
|
-
private apiRequest;
|
|
73
94
|
private fetchWithTimeout;
|
|
74
95
|
private parseTokenResponse;
|
|
75
96
|
private handleTokenError;
|
|
76
97
|
private handleApiErrorFromBody;
|
|
77
98
|
}
|
|
78
99
|
|
|
79
|
-
export { EdgeConnectServer, type EdgeConnectServerConfig, type RequestInfo, type ResponseInfo, type RetryConfig, type TransferOptions };
|
|
100
|
+
export { EdgeConnectServer, type EdgeConnectServerConfig, EdgeUserClient, type RequestInfo, type ResponseInfo, type RetryConfig, type TransferOptions };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EdgeEnvironment,
|
|
1
|
+
import { EdgeEnvironment, User, VerifyIdentityOptions, VerifyIdentityResult, Balance, Transfer, ListTransfersParams, TransferList, EdgeTokens } from '@edge-markets/connect';
|
|
2
2
|
export { Balance, EdgeApiError, EdgeAuthenticationError, EdgeConsentRequiredError, EdgeEnvironment, EdgeError, EdgeInsufficientScopeError, EdgeNetworkError, EdgeNotFoundError, EdgeTokenExchangeError, EdgeTokens, ListTransfersParams, Transfer, TransferList, TransferListItem, TransferStatus, TransferType, User, getEnvironmentConfig, isApiError, isAuthenticationError, isConsentRequiredError, isEdgeError, isNetworkError, isProductionEnvironment } from '@edge-markets/connect';
|
|
3
3
|
|
|
4
4
|
interface RequestInfo {
|
|
@@ -46,6 +46,29 @@ interface TransferOptions {
|
|
|
46
46
|
amount: string;
|
|
47
47
|
idempotencyKey: string;
|
|
48
48
|
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* A lightweight, user-scoped API client created via {@link EdgeConnectServer.forUser}.
|
|
52
|
+
*
|
|
53
|
+
* Each instance holds a single access token and delegates the actual HTTP work
|
|
54
|
+
* to its parent {@link EdgeConnectServer}.
|
|
55
|
+
*/
|
|
56
|
+
declare class EdgeUserClient {
|
|
57
|
+
private readonly server;
|
|
58
|
+
readonly accessToken: string;
|
|
59
|
+
constructor(server: EdgeConnectServer, accessToken: string);
|
|
60
|
+
getUser(): Promise<User>;
|
|
61
|
+
verifyIdentity(options: VerifyIdentityOptions): Promise<VerifyIdentityResult>;
|
|
62
|
+
getBalance(): Promise<Balance>;
|
|
63
|
+
initiateTransfer(options: TransferOptions): Promise<Transfer>;
|
|
64
|
+
verifyTransfer(transferId: string, otp: string): Promise<Transfer>;
|
|
65
|
+
getTransfer(transferId: string): Promise<Transfer>;
|
|
66
|
+
listTransfers(params?: ListTransfersParams): Promise<TransferList>;
|
|
67
|
+
revokeConsent(): Promise<{
|
|
68
|
+
revoked: boolean;
|
|
69
|
+
}>;
|
|
70
|
+
}
|
|
71
|
+
|
|
49
72
|
declare class EdgeConnectServer {
|
|
50
73
|
private readonly config;
|
|
51
74
|
private readonly apiBaseUrl;
|
|
@@ -55,25 +78,23 @@ declare class EdgeConnectServer {
|
|
|
55
78
|
static getInstance(config: EdgeConnectServerConfig): EdgeConnectServer;
|
|
56
79
|
static clearInstances(): void;
|
|
57
80
|
constructor(config: EdgeConnectServerConfig);
|
|
81
|
+
/**
|
|
82
|
+
* Create a user-scoped client for making authenticated API calls.
|
|
83
|
+
*
|
|
84
|
+
* The returned {@link EdgeUserClient} is lightweight — create one per request
|
|
85
|
+
* or per user session and discard it when the token changes.
|
|
86
|
+
*/
|
|
87
|
+
forUser(accessToken: string): EdgeUserClient;
|
|
58
88
|
exchangeCode(code: string, codeVerifier: string, redirectUri?: string): Promise<EdgeTokens>;
|
|
59
89
|
refreshTokens(refreshToken: string): Promise<EdgeTokens>;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
initiateTransfer(accessToken: string, options: TransferOptions): Promise<Transfer>;
|
|
63
|
-
private validateTransferOptions;
|
|
64
|
-
verifyTransfer(accessToken: string, transferId: string, otp: string): Promise<Transfer>;
|
|
65
|
-
getTransfer(accessToken: string, transferId: string): Promise<Transfer>;
|
|
66
|
-
listTransfers(accessToken: string, params?: ListTransfersParams): Promise<TransferList>;
|
|
67
|
-
revokeConsent(accessToken: string): Promise<{
|
|
68
|
-
revoked: boolean;
|
|
69
|
-
}>;
|
|
90
|
+
/** @internal Called by {@link EdgeUserClient} — not part of the public API. */
|
|
91
|
+
_apiRequest<T>(method: string, path: string, accessToken: string, body?: unknown): Promise<T>;
|
|
70
92
|
private getRetryDelay;
|
|
71
93
|
private sleep;
|
|
72
|
-
private apiRequest;
|
|
73
94
|
private fetchWithTimeout;
|
|
74
95
|
private parseTokenResponse;
|
|
75
96
|
private handleTokenError;
|
|
76
97
|
private handleApiErrorFromBody;
|
|
77
98
|
}
|
|
78
99
|
|
|
79
|
-
export { EdgeConnectServer, type EdgeConnectServerConfig, type RequestInfo, type ResponseInfo, type RetryConfig, type TransferOptions };
|
|
100
|
+
export { EdgeConnectServer, type EdgeConnectServerConfig, EdgeUserClient, type RequestInfo, type ResponseInfo, type RetryConfig, type TransferOptions };
|
package/dist/index.js
CHANGED
|
@@ -20,27 +20,125 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
EdgeApiError: () =>
|
|
24
|
-
EdgeAuthenticationError: () =>
|
|
23
|
+
EdgeApiError: () => import_connect3.EdgeApiError,
|
|
24
|
+
EdgeAuthenticationError: () => import_connect3.EdgeAuthenticationError,
|
|
25
25
|
EdgeConnectServer: () => EdgeConnectServer,
|
|
26
|
-
EdgeConsentRequiredError: () =>
|
|
27
|
-
EdgeError: () =>
|
|
28
|
-
EdgeInsufficientScopeError: () =>
|
|
29
|
-
EdgeNetworkError: () =>
|
|
30
|
-
EdgeNotFoundError: () =>
|
|
31
|
-
EdgeTokenExchangeError: () =>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
26
|
+
EdgeConsentRequiredError: () => import_connect3.EdgeConsentRequiredError,
|
|
27
|
+
EdgeError: () => import_connect3.EdgeError,
|
|
28
|
+
EdgeInsufficientScopeError: () => import_connect3.EdgeInsufficientScopeError,
|
|
29
|
+
EdgeNetworkError: () => import_connect3.EdgeNetworkError,
|
|
30
|
+
EdgeNotFoundError: () => import_connect3.EdgeNotFoundError,
|
|
31
|
+
EdgeTokenExchangeError: () => import_connect3.EdgeTokenExchangeError,
|
|
32
|
+
EdgeUserClient: () => EdgeUserClient,
|
|
33
|
+
getEnvironmentConfig: () => import_connect4.getEnvironmentConfig,
|
|
34
|
+
isApiError: () => import_connect3.isApiError,
|
|
35
|
+
isAuthenticationError: () => import_connect3.isAuthenticationError,
|
|
36
|
+
isConsentRequiredError: () => import_connect3.isConsentRequiredError,
|
|
37
|
+
isEdgeError: () => import_connect3.isEdgeError,
|
|
38
|
+
isNetworkError: () => import_connect3.isNetworkError,
|
|
39
|
+
isProductionEnvironment: () => import_connect4.isProductionEnvironment
|
|
39
40
|
});
|
|
40
41
|
module.exports = __toCommonJS(index_exports);
|
|
41
42
|
|
|
42
43
|
// src/edge-connect-server.ts
|
|
44
|
+
var import_connect2 = require("@edge-markets/connect");
|
|
45
|
+
|
|
46
|
+
// src/validators.ts
|
|
43
47
|
var import_connect = require("@edge-markets/connect");
|
|
48
|
+
function validateVerifyIdentityOptions(options) {
|
|
49
|
+
const errors = {};
|
|
50
|
+
const firstName = typeof options.firstName === "string" ? options.firstName.trim() : "";
|
|
51
|
+
if (!firstName) {
|
|
52
|
+
errors.firstName = ["firstName is required for identity verification"];
|
|
53
|
+
}
|
|
54
|
+
const lastName = typeof options.lastName === "string" ? options.lastName.trim() : "";
|
|
55
|
+
if (!lastName) {
|
|
56
|
+
errors.lastName = ["lastName is required for identity verification"];
|
|
57
|
+
}
|
|
58
|
+
if (!options.address || typeof options.address !== "object" || Array.isArray(options.address)) {
|
|
59
|
+
errors.address = ["address is required for identity verification"];
|
|
60
|
+
}
|
|
61
|
+
if (Object.keys(errors).length > 0) {
|
|
62
|
+
const firstMessage = Object.values(errors)[0][0];
|
|
63
|
+
throw new import_connect.EdgeValidationError(firstMessage, errors);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function validateTransferOptions(options) {
|
|
67
|
+
const errors = {};
|
|
68
|
+
if (!options.type || !["debit", "credit"].includes(options.type)) {
|
|
69
|
+
errors.type = ['Must be "debit" or "credit"'];
|
|
70
|
+
}
|
|
71
|
+
if (!options.amount) {
|
|
72
|
+
errors.amount = ["Amount is required"];
|
|
73
|
+
} else {
|
|
74
|
+
const amount = parseFloat(options.amount);
|
|
75
|
+
if (isNaN(amount)) {
|
|
76
|
+
errors.amount = ["Must be a valid number"];
|
|
77
|
+
} else if (amount <= 0) {
|
|
78
|
+
errors.amount = ["Must be greater than 0"];
|
|
79
|
+
} else if (!/^\d+(\.\d{1,2})?$/.test(options.amount)) {
|
|
80
|
+
errors.amount = ["Must have at most 2 decimal places"];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (!options.idempotencyKey || options.idempotencyKey.trim() === "") {
|
|
84
|
+
errors.idempotencyKey = ["idempotencyKey is required"];
|
|
85
|
+
} else if (options.idempotencyKey.length > 255) {
|
|
86
|
+
errors.idempotencyKey = ["Must be 255 characters or less"];
|
|
87
|
+
}
|
|
88
|
+
if (Object.keys(errors).length > 0) {
|
|
89
|
+
throw new import_connect.EdgeValidationError("Invalid transfer options", errors);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/edge-user-client.ts
|
|
94
|
+
var EdgeUserClient = class {
|
|
95
|
+
constructor(server, accessToken) {
|
|
96
|
+
this.server = server;
|
|
97
|
+
this.accessToken = accessToken;
|
|
98
|
+
}
|
|
99
|
+
async getUser() {
|
|
100
|
+
return this.server._apiRequest("GET", "/user", this.accessToken);
|
|
101
|
+
}
|
|
102
|
+
async verifyIdentity(options) {
|
|
103
|
+
validateVerifyIdentityOptions(options);
|
|
104
|
+
return this.server._apiRequest("POST", "/user/verify-identity", this.accessToken, options);
|
|
105
|
+
}
|
|
106
|
+
async getBalance() {
|
|
107
|
+
return this.server._apiRequest("GET", "/balance", this.accessToken);
|
|
108
|
+
}
|
|
109
|
+
async initiateTransfer(options) {
|
|
110
|
+
validateTransferOptions(options);
|
|
111
|
+
const body = {
|
|
112
|
+
type: options.type,
|
|
113
|
+
amount: options.amount,
|
|
114
|
+
idempotencyKey: options.idempotencyKey
|
|
115
|
+
};
|
|
116
|
+
return this.server._apiRequest("POST", "/transfer", this.accessToken, body);
|
|
117
|
+
}
|
|
118
|
+
async verifyTransfer(transferId, otp) {
|
|
119
|
+
return this.server._apiRequest(
|
|
120
|
+
"POST",
|
|
121
|
+
`/transfer/${encodeURIComponent(transferId)}/verify`,
|
|
122
|
+
this.accessToken,
|
|
123
|
+
{ otp }
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
async getTransfer(transferId) {
|
|
127
|
+
return this.server._apiRequest("GET", `/transfer/${encodeURIComponent(transferId)}`, this.accessToken);
|
|
128
|
+
}
|
|
129
|
+
async listTransfers(params) {
|
|
130
|
+
const queryParams = new URLSearchParams();
|
|
131
|
+
if (params?.limit) queryParams.set("limit", String(params.limit));
|
|
132
|
+
if (params?.offset) queryParams.set("offset", String(params.offset));
|
|
133
|
+
if (params?.status) queryParams.set("status", params.status);
|
|
134
|
+
const query = queryParams.toString();
|
|
135
|
+
const path = query ? `/transfers?${query}` : "/transfers";
|
|
136
|
+
return this.server._apiRequest("GET", path, this.accessToken);
|
|
137
|
+
}
|
|
138
|
+
async revokeConsent() {
|
|
139
|
+
return this.server._apiRequest("DELETE", "/consent", this.accessToken);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
44
142
|
|
|
45
143
|
// src/mle.ts
|
|
46
144
|
var import_crypto = require("crypto");
|
|
@@ -117,7 +215,7 @@ function fromBase64Url(value) {
|
|
|
117
215
|
return Buffer.from(normalized, "base64");
|
|
118
216
|
}
|
|
119
217
|
|
|
120
|
-
// src/
|
|
218
|
+
// src/types.ts
|
|
121
219
|
var DEFAULT_TIMEOUT = 3e4;
|
|
122
220
|
var USER_AGENT = "@edge-markets/connect-node/1.0.0";
|
|
123
221
|
var DEFAULT_RETRY_CONFIG = {
|
|
@@ -126,6 +224,8 @@ var DEFAULT_RETRY_CONFIG = {
|
|
|
126
224
|
backoff: "exponential",
|
|
127
225
|
baseDelayMs: 1e3
|
|
128
226
|
};
|
|
227
|
+
|
|
228
|
+
// src/edge-connect-server.ts
|
|
129
229
|
var instances = /* @__PURE__ */ new Map();
|
|
130
230
|
function getInstanceKey(config) {
|
|
131
231
|
return `${config.clientId}:${config.environment}`;
|
|
@@ -153,7 +253,7 @@ var EdgeConnectServer = class _EdgeConnectServer {
|
|
|
153
253
|
throw new Error("EdgeConnectServer: environment is required");
|
|
154
254
|
}
|
|
155
255
|
this.config = config;
|
|
156
|
-
const envConfig = (0,
|
|
256
|
+
const envConfig = (0, import_connect2.getEnvironmentConfig)(config.environment);
|
|
157
257
|
this.apiBaseUrl = config.apiBaseUrl || envConfig.apiBaseUrl;
|
|
158
258
|
this.oauthBaseUrl = config.oauthBaseUrl || envConfig.oauthBaseUrl;
|
|
159
259
|
this.timeout = config.timeout || DEFAULT_TIMEOUT;
|
|
@@ -162,6 +262,18 @@ var EdgeConnectServer = class _EdgeConnectServer {
|
|
|
162
262
|
...config.retry
|
|
163
263
|
};
|
|
164
264
|
}
|
|
265
|
+
/**
|
|
266
|
+
* Create a user-scoped client for making authenticated API calls.
|
|
267
|
+
*
|
|
268
|
+
* The returned {@link EdgeUserClient} is lightweight — create one per request
|
|
269
|
+
* or per user session and discard it when the token changes.
|
|
270
|
+
*/
|
|
271
|
+
forUser(accessToken) {
|
|
272
|
+
if (!accessToken) {
|
|
273
|
+
throw new import_connect2.EdgeAuthenticationError("accessToken is required when calling forUser()");
|
|
274
|
+
}
|
|
275
|
+
return new EdgeUserClient(this, accessToken);
|
|
276
|
+
}
|
|
165
277
|
async exchangeCode(code, codeVerifier, redirectUri) {
|
|
166
278
|
const tokenUrl = `${this.oauthBaseUrl}/token`;
|
|
167
279
|
const body = {
|
|
@@ -188,8 +300,8 @@ var EdgeConnectServer = class _EdgeConnectServer {
|
|
|
188
300
|
const data = await response.json();
|
|
189
301
|
return this.parseTokenResponse(data);
|
|
190
302
|
} catch (error) {
|
|
191
|
-
if (error instanceof
|
|
192
|
-
throw new
|
|
303
|
+
if (error instanceof import_connect2.EdgeError) throw error;
|
|
304
|
+
throw new import_connect2.EdgeNetworkError("Failed to exchange code", error);
|
|
193
305
|
}
|
|
194
306
|
}
|
|
195
307
|
async refreshTokens(refreshToken) {
|
|
@@ -211,7 +323,7 @@ var EdgeConnectServer = class _EdgeConnectServer {
|
|
|
211
323
|
});
|
|
212
324
|
if (!response.ok) {
|
|
213
325
|
const error = await response.json().catch(() => ({}));
|
|
214
|
-
throw new
|
|
326
|
+
throw new import_connect2.EdgeAuthenticationError(
|
|
215
327
|
error.message || error.error_description || "Token refresh failed. User may need to reconnect.",
|
|
216
328
|
{ tokenError: error }
|
|
217
329
|
);
|
|
@@ -219,89 +331,12 @@ var EdgeConnectServer = class _EdgeConnectServer {
|
|
|
219
331
|
const data = await response.json();
|
|
220
332
|
return this.parseTokenResponse(data, refreshToken);
|
|
221
333
|
} catch (error) {
|
|
222
|
-
if (error instanceof
|
|
223
|
-
throw new
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
async getUser(accessToken) {
|
|
227
|
-
return this.apiRequest("GET", "/user", accessToken);
|
|
228
|
-
}
|
|
229
|
-
async getBalance(accessToken) {
|
|
230
|
-
return this.apiRequest("GET", "/balance", accessToken);
|
|
231
|
-
}
|
|
232
|
-
async initiateTransfer(accessToken, options) {
|
|
233
|
-
this.validateTransferOptions(options);
|
|
234
|
-
const body = {
|
|
235
|
-
type: options.type,
|
|
236
|
-
amount: options.amount,
|
|
237
|
-
idempotencyKey: options.idempotencyKey
|
|
238
|
-
};
|
|
239
|
-
return this.apiRequest("POST", "/transfer", accessToken, body);
|
|
240
|
-
}
|
|
241
|
-
validateTransferOptions(options) {
|
|
242
|
-
const errors = {};
|
|
243
|
-
if (!options.type || !["debit", "credit"].includes(options.type)) {
|
|
244
|
-
errors.type = ['Must be "debit" or "credit"'];
|
|
245
|
-
}
|
|
246
|
-
if (!options.amount) {
|
|
247
|
-
errors.amount = ["Amount is required"];
|
|
248
|
-
} else {
|
|
249
|
-
const amount = parseFloat(options.amount);
|
|
250
|
-
if (isNaN(amount)) {
|
|
251
|
-
errors.amount = ["Must be a valid number"];
|
|
252
|
-
} else if (amount <= 0) {
|
|
253
|
-
errors.amount = ["Must be greater than 0"];
|
|
254
|
-
} else if (!/^\d+(\.\d{1,2})?$/.test(options.amount)) {
|
|
255
|
-
errors.amount = ["Must have at most 2 decimal places"];
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
if (!options.idempotencyKey || options.idempotencyKey.trim() === "") {
|
|
259
|
-
errors.idempotencyKey = ["idempotencyKey is required"];
|
|
260
|
-
} else if (options.idempotencyKey.length > 255) {
|
|
261
|
-
errors.idempotencyKey = ["Must be 255 characters or less"];
|
|
262
|
-
}
|
|
263
|
-
if (Object.keys(errors).length > 0) {
|
|
264
|
-
throw new import_connect.EdgeValidationError("Invalid transfer options", errors);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
async verifyTransfer(accessToken, transferId, otp) {
|
|
268
|
-
return this.apiRequest(
|
|
269
|
-
"POST",
|
|
270
|
-
`/transfer/${encodeURIComponent(transferId)}/verify`,
|
|
271
|
-
accessToken,
|
|
272
|
-
{ otp }
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
async getTransfer(accessToken, transferId) {
|
|
276
|
-
return this.apiRequest(
|
|
277
|
-
"GET",
|
|
278
|
-
`/transfer/${encodeURIComponent(transferId)}`,
|
|
279
|
-
accessToken
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
async listTransfers(accessToken, params) {
|
|
283
|
-
const queryParams = new URLSearchParams();
|
|
284
|
-
if (params?.limit) queryParams.set("limit", String(params.limit));
|
|
285
|
-
if (params?.offset) queryParams.set("offset", String(params.offset));
|
|
286
|
-
if (params?.status) queryParams.set("status", params.status);
|
|
287
|
-
const query = queryParams.toString();
|
|
288
|
-
const path = query ? `/transfers?${query}` : "/transfers";
|
|
289
|
-
return this.apiRequest("GET", path, accessToken);
|
|
290
|
-
}
|
|
291
|
-
async revokeConsent(accessToken) {
|
|
292
|
-
return this.apiRequest("DELETE", "/consent", accessToken);
|
|
293
|
-
}
|
|
294
|
-
getRetryDelay(attempt) {
|
|
295
|
-
const { backoff, baseDelayMs } = this.retryConfig;
|
|
296
|
-
if (backoff === "exponential") {
|
|
297
|
-
return baseDelayMs * Math.pow(2, attempt);
|
|
334
|
+
if (error instanceof import_connect2.EdgeError) throw error;
|
|
335
|
+
throw new import_connect2.EdgeNetworkError("Failed to refresh tokens", error);
|
|
298
336
|
}
|
|
299
|
-
return baseDelayMs * (attempt + 1);
|
|
300
337
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
async apiRequest(method, path, accessToken, body) {
|
|
338
|
+
/** @internal Called by {@link EdgeUserClient} — not part of the public API. */
|
|
339
|
+
async _apiRequest(method, path, accessToken, body) {
|
|
305
340
|
const url = `${this.apiBaseUrl}${path}`;
|
|
306
341
|
let lastError = null;
|
|
307
342
|
const mleConfig = this.config.mle;
|
|
@@ -335,14 +370,14 @@ var EdgeConnectServer = class _EdgeConnectServer {
|
|
|
335
370
|
if (mleEnabled && typeof rawResponseBody?.jwe === "string") {
|
|
336
371
|
responseBody = decryptMle(rawResponseBody.jwe, mleConfig);
|
|
337
372
|
} else if (!mleEnabled && typeof rawResponseBody?.jwe === "string") {
|
|
338
|
-
throw new
|
|
373
|
+
throw new import_connect2.EdgeApiError(
|
|
339
374
|
"mle_required",
|
|
340
375
|
"The API responded with message-level encryption. Enable MLE in SDK config.",
|
|
341
376
|
response.status,
|
|
342
377
|
rawResponseBody
|
|
343
378
|
);
|
|
344
379
|
} else if (mleEnabled && mleConfig?.strictResponseEncryption !== false && response.ok && typeof rawResponseBody?.jwe !== "string") {
|
|
345
|
-
throw new
|
|
380
|
+
throw new import_connect2.EdgeApiError(
|
|
346
381
|
"mle_response_missing",
|
|
347
382
|
"Expected encrypted response payload but received plaintext.",
|
|
348
383
|
response.status,
|
|
@@ -360,8 +395,8 @@ var EdgeConnectServer = class _EdgeConnectServer {
|
|
|
360
395
|
}
|
|
361
396
|
return responseBody;
|
|
362
397
|
} catch (error) {
|
|
363
|
-
if (error instanceof
|
|
364
|
-
if (!(error instanceof
|
|
398
|
+
if (error instanceof import_connect2.EdgeError) {
|
|
399
|
+
if (!(error instanceof import_connect2.EdgeNetworkError) || attempt >= this.retryConfig.maxRetries) {
|
|
365
400
|
throw error;
|
|
366
401
|
}
|
|
367
402
|
lastError = error;
|
|
@@ -369,11 +404,24 @@ var EdgeConnectServer = class _EdgeConnectServer {
|
|
|
369
404
|
}
|
|
370
405
|
lastError = error;
|
|
371
406
|
if (attempt >= this.retryConfig.maxRetries) {
|
|
372
|
-
throw new
|
|
407
|
+
throw new import_connect2.EdgeNetworkError(`API request failed: ${method} ${path}`, lastError);
|
|
373
408
|
}
|
|
374
409
|
}
|
|
375
410
|
}
|
|
376
|
-
throw new
|
|
411
|
+
throw new import_connect2.EdgeNetworkError(
|
|
412
|
+
`API request failed after ${this.retryConfig.maxRetries} retries: ${method} ${path}`,
|
|
413
|
+
lastError
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
getRetryDelay(attempt) {
|
|
417
|
+
const { backoff, baseDelayMs } = this.retryConfig;
|
|
418
|
+
if (backoff === "exponential") {
|
|
419
|
+
return baseDelayMs * Math.pow(2, attempt);
|
|
420
|
+
}
|
|
421
|
+
return baseDelayMs * (attempt + 1);
|
|
422
|
+
}
|
|
423
|
+
async sleep(ms) {
|
|
424
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
377
425
|
}
|
|
378
426
|
async fetchWithTimeout(url, options) {
|
|
379
427
|
const controller = new AbortController();
|
|
@@ -401,45 +449,37 @@ var EdgeConnectServer = class _EdgeConnectServer {
|
|
|
401
449
|
const errorCode = error.error;
|
|
402
450
|
const errorMessage = error.message || error.error_description;
|
|
403
451
|
if (errorCode === "invalid_grant" || errorMessage?.includes("Invalid or expired")) {
|
|
404
|
-
return new
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
);
|
|
452
|
+
return new import_connect2.EdgeTokenExchangeError("Authorization code is invalid, expired, or already used. Please try again.", {
|
|
453
|
+
edgeBoostError: error
|
|
454
|
+
});
|
|
408
455
|
}
|
|
409
456
|
if (errorCode === "invalid_client" || errorMessage?.includes("Invalid client")) {
|
|
410
|
-
return new
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
);
|
|
457
|
+
return new import_connect2.EdgeAuthenticationError("Invalid client credentials. Check your client ID and secret.", {
|
|
458
|
+
edgeBoostError: error
|
|
459
|
+
});
|
|
414
460
|
}
|
|
415
461
|
if (errorMessage?.includes("code_verifier") || errorMessage?.includes("PKCE")) {
|
|
416
|
-
return new
|
|
417
|
-
"PKCE verification failed. Please try again.",
|
|
418
|
-
{ edgeBoostError: error }
|
|
419
|
-
);
|
|
462
|
+
return new import_connect2.EdgeTokenExchangeError("PKCE verification failed. Please try again.", { edgeBoostError: error });
|
|
420
463
|
}
|
|
421
|
-
return new
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
);
|
|
464
|
+
return new import_connect2.EdgeTokenExchangeError(errorMessage || "Failed to exchange authorization code", {
|
|
465
|
+
edgeBoostError: error,
|
|
466
|
+
statusCode: status
|
|
467
|
+
});
|
|
425
468
|
}
|
|
426
469
|
async handleApiErrorFromBody(error, status, path) {
|
|
427
470
|
if (status === 401) {
|
|
428
|
-
return new
|
|
429
|
-
error.message || "Access token is invalid or expired",
|
|
430
|
-
error
|
|
431
|
-
);
|
|
471
|
+
return new import_connect2.EdgeAuthenticationError(error.message || "Access token is invalid or expired", error);
|
|
432
472
|
}
|
|
433
473
|
if (status === 403) {
|
|
434
474
|
if (error.error === "consent_required") {
|
|
435
|
-
return new
|
|
475
|
+
return new import_connect2.EdgeConsentRequiredError(
|
|
436
476
|
this.config.clientId,
|
|
437
477
|
error.consentUrl,
|
|
438
478
|
error.message
|
|
439
479
|
);
|
|
440
480
|
}
|
|
441
481
|
if (error.error === "insufficient_scope" || error.error === "insufficient_consent") {
|
|
442
|
-
return new
|
|
482
|
+
return new import_connect2.EdgeInsufficientScopeError(
|
|
443
483
|
error.missing_scopes || error.missingScopes || [],
|
|
444
484
|
error.message
|
|
445
485
|
);
|
|
@@ -448,9 +488,15 @@ var EdgeConnectServer = class _EdgeConnectServer {
|
|
|
448
488
|
if (status === 404) {
|
|
449
489
|
const resourceType = path.includes("/transfer") ? "Transfer" : "Resource";
|
|
450
490
|
const resourceId = path.split("/").pop() || "unknown";
|
|
451
|
-
return new
|
|
491
|
+
return new import_connect2.EdgeNotFoundError(resourceType, resourceId);
|
|
452
492
|
}
|
|
453
|
-
|
|
493
|
+
if (status === 422 && error.error === "identity_verification_failed") {
|
|
494
|
+
return new import_connect2.EdgeIdentityVerificationError(
|
|
495
|
+
error.fieldErrors || {},
|
|
496
|
+
error.message
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
return new import_connect2.EdgeApiError(
|
|
454
500
|
error.error || "api_error",
|
|
455
501
|
error.message || error.error_description || `Request failed with status ${status}`,
|
|
456
502
|
status,
|
|
@@ -460,8 +506,8 @@ var EdgeConnectServer = class _EdgeConnectServer {
|
|
|
460
506
|
};
|
|
461
507
|
|
|
462
508
|
// src/index.ts
|
|
463
|
-
var import_connect2 = require("@edge-markets/connect");
|
|
464
509
|
var import_connect3 = require("@edge-markets/connect");
|
|
510
|
+
var import_connect4 = require("@edge-markets/connect");
|
|
465
511
|
// Annotate the CommonJS export names for ESM import in node:
|
|
466
512
|
0 && (module.exports = {
|
|
467
513
|
EdgeApiError,
|
|
@@ -473,6 +519,7 @@ var import_connect3 = require("@edge-markets/connect");
|
|
|
473
519
|
EdgeNetworkError,
|
|
474
520
|
EdgeNotFoundError,
|
|
475
521
|
EdgeTokenExchangeError,
|
|
522
|
+
EdgeUserClient,
|
|
476
523
|
getEnvironmentConfig,
|
|
477
524
|
isApiError,
|
|
478
525
|
isAuthenticationError,
|
package/dist/index.mjs
CHANGED
|
@@ -1,17 +1,114 @@
|
|
|
1
1
|
// src/edge-connect-server.ts
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
EdgeError,
|
|
3
|
+
EdgeApiError,
|
|
5
4
|
EdgeAuthenticationError,
|
|
6
|
-
EdgeTokenExchangeError,
|
|
7
5
|
EdgeConsentRequiredError,
|
|
6
|
+
EdgeError,
|
|
7
|
+
EdgeIdentityVerificationError,
|
|
8
8
|
EdgeInsufficientScopeError,
|
|
9
|
-
EdgeApiError,
|
|
10
|
-
EdgeNotFoundError,
|
|
11
9
|
EdgeNetworkError,
|
|
12
|
-
|
|
10
|
+
EdgeNotFoundError,
|
|
11
|
+
EdgeTokenExchangeError,
|
|
12
|
+
getEnvironmentConfig
|
|
13
13
|
} from "@edge-markets/connect";
|
|
14
14
|
|
|
15
|
+
// src/validators.ts
|
|
16
|
+
import { EdgeValidationError } from "@edge-markets/connect";
|
|
17
|
+
function validateVerifyIdentityOptions(options) {
|
|
18
|
+
const errors = {};
|
|
19
|
+
const firstName = typeof options.firstName === "string" ? options.firstName.trim() : "";
|
|
20
|
+
if (!firstName) {
|
|
21
|
+
errors.firstName = ["firstName is required for identity verification"];
|
|
22
|
+
}
|
|
23
|
+
const lastName = typeof options.lastName === "string" ? options.lastName.trim() : "";
|
|
24
|
+
if (!lastName) {
|
|
25
|
+
errors.lastName = ["lastName is required for identity verification"];
|
|
26
|
+
}
|
|
27
|
+
if (!options.address || typeof options.address !== "object" || Array.isArray(options.address)) {
|
|
28
|
+
errors.address = ["address is required for identity verification"];
|
|
29
|
+
}
|
|
30
|
+
if (Object.keys(errors).length > 0) {
|
|
31
|
+
const firstMessage = Object.values(errors)[0][0];
|
|
32
|
+
throw new EdgeValidationError(firstMessage, errors);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function validateTransferOptions(options) {
|
|
36
|
+
const errors = {};
|
|
37
|
+
if (!options.type || !["debit", "credit"].includes(options.type)) {
|
|
38
|
+
errors.type = ['Must be "debit" or "credit"'];
|
|
39
|
+
}
|
|
40
|
+
if (!options.amount) {
|
|
41
|
+
errors.amount = ["Amount is required"];
|
|
42
|
+
} else {
|
|
43
|
+
const amount = parseFloat(options.amount);
|
|
44
|
+
if (isNaN(amount)) {
|
|
45
|
+
errors.amount = ["Must be a valid number"];
|
|
46
|
+
} else if (amount <= 0) {
|
|
47
|
+
errors.amount = ["Must be greater than 0"];
|
|
48
|
+
} else if (!/^\d+(\.\d{1,2})?$/.test(options.amount)) {
|
|
49
|
+
errors.amount = ["Must have at most 2 decimal places"];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (!options.idempotencyKey || options.idempotencyKey.trim() === "") {
|
|
53
|
+
errors.idempotencyKey = ["idempotencyKey is required"];
|
|
54
|
+
} else if (options.idempotencyKey.length > 255) {
|
|
55
|
+
errors.idempotencyKey = ["Must be 255 characters or less"];
|
|
56
|
+
}
|
|
57
|
+
if (Object.keys(errors).length > 0) {
|
|
58
|
+
throw new EdgeValidationError("Invalid transfer options", errors);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/edge-user-client.ts
|
|
63
|
+
var EdgeUserClient = class {
|
|
64
|
+
constructor(server, accessToken) {
|
|
65
|
+
this.server = server;
|
|
66
|
+
this.accessToken = accessToken;
|
|
67
|
+
}
|
|
68
|
+
async getUser() {
|
|
69
|
+
return this.server._apiRequest("GET", "/user", this.accessToken);
|
|
70
|
+
}
|
|
71
|
+
async verifyIdentity(options) {
|
|
72
|
+
validateVerifyIdentityOptions(options);
|
|
73
|
+
return this.server._apiRequest("POST", "/user/verify-identity", this.accessToken, options);
|
|
74
|
+
}
|
|
75
|
+
async getBalance() {
|
|
76
|
+
return this.server._apiRequest("GET", "/balance", this.accessToken);
|
|
77
|
+
}
|
|
78
|
+
async initiateTransfer(options) {
|
|
79
|
+
validateTransferOptions(options);
|
|
80
|
+
const body = {
|
|
81
|
+
type: options.type,
|
|
82
|
+
amount: options.amount,
|
|
83
|
+
idempotencyKey: options.idempotencyKey
|
|
84
|
+
};
|
|
85
|
+
return this.server._apiRequest("POST", "/transfer", this.accessToken, body);
|
|
86
|
+
}
|
|
87
|
+
async verifyTransfer(transferId, otp) {
|
|
88
|
+
return this.server._apiRequest(
|
|
89
|
+
"POST",
|
|
90
|
+
`/transfer/${encodeURIComponent(transferId)}/verify`,
|
|
91
|
+
this.accessToken,
|
|
92
|
+
{ otp }
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
async getTransfer(transferId) {
|
|
96
|
+
return this.server._apiRequest("GET", `/transfer/${encodeURIComponent(transferId)}`, this.accessToken);
|
|
97
|
+
}
|
|
98
|
+
async listTransfers(params) {
|
|
99
|
+
const queryParams = new URLSearchParams();
|
|
100
|
+
if (params?.limit) queryParams.set("limit", String(params.limit));
|
|
101
|
+
if (params?.offset) queryParams.set("offset", String(params.offset));
|
|
102
|
+
if (params?.status) queryParams.set("status", params.status);
|
|
103
|
+
const query = queryParams.toString();
|
|
104
|
+
const path = query ? `/transfers?${query}` : "/transfers";
|
|
105
|
+
return this.server._apiRequest("GET", path, this.accessToken);
|
|
106
|
+
}
|
|
107
|
+
async revokeConsent() {
|
|
108
|
+
return this.server._apiRequest("DELETE", "/consent", this.accessToken);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
15
112
|
// src/mle.ts
|
|
16
113
|
import { randomUUID, randomBytes, createCipheriv, createDecipheriv, publicEncrypt, privateDecrypt, constants } from "crypto";
|
|
17
114
|
function encryptMle(payload, clientId, config) {
|
|
@@ -87,7 +184,7 @@ function fromBase64Url(value) {
|
|
|
87
184
|
return Buffer.from(normalized, "base64");
|
|
88
185
|
}
|
|
89
186
|
|
|
90
|
-
// src/
|
|
187
|
+
// src/types.ts
|
|
91
188
|
var DEFAULT_TIMEOUT = 3e4;
|
|
92
189
|
var USER_AGENT = "@edge-markets/connect-node/1.0.0";
|
|
93
190
|
var DEFAULT_RETRY_CONFIG = {
|
|
@@ -96,6 +193,8 @@ var DEFAULT_RETRY_CONFIG = {
|
|
|
96
193
|
backoff: "exponential",
|
|
97
194
|
baseDelayMs: 1e3
|
|
98
195
|
};
|
|
196
|
+
|
|
197
|
+
// src/edge-connect-server.ts
|
|
99
198
|
var instances = /* @__PURE__ */ new Map();
|
|
100
199
|
function getInstanceKey(config) {
|
|
101
200
|
return `${config.clientId}:${config.environment}`;
|
|
@@ -132,6 +231,18 @@ var EdgeConnectServer = class _EdgeConnectServer {
|
|
|
132
231
|
...config.retry
|
|
133
232
|
};
|
|
134
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Create a user-scoped client for making authenticated API calls.
|
|
236
|
+
*
|
|
237
|
+
* The returned {@link EdgeUserClient} is lightweight — create one per request
|
|
238
|
+
* or per user session and discard it when the token changes.
|
|
239
|
+
*/
|
|
240
|
+
forUser(accessToken) {
|
|
241
|
+
if (!accessToken) {
|
|
242
|
+
throw new EdgeAuthenticationError("accessToken is required when calling forUser()");
|
|
243
|
+
}
|
|
244
|
+
return new EdgeUserClient(this, accessToken);
|
|
245
|
+
}
|
|
135
246
|
async exchangeCode(code, codeVerifier, redirectUri) {
|
|
136
247
|
const tokenUrl = `${this.oauthBaseUrl}/token`;
|
|
137
248
|
const body = {
|
|
@@ -193,85 +304,8 @@ var EdgeConnectServer = class _EdgeConnectServer {
|
|
|
193
304
|
throw new EdgeNetworkError("Failed to refresh tokens", error);
|
|
194
305
|
}
|
|
195
306
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
async getBalance(accessToken) {
|
|
200
|
-
return this.apiRequest("GET", "/balance", accessToken);
|
|
201
|
-
}
|
|
202
|
-
async initiateTransfer(accessToken, options) {
|
|
203
|
-
this.validateTransferOptions(options);
|
|
204
|
-
const body = {
|
|
205
|
-
type: options.type,
|
|
206
|
-
amount: options.amount,
|
|
207
|
-
idempotencyKey: options.idempotencyKey
|
|
208
|
-
};
|
|
209
|
-
return this.apiRequest("POST", "/transfer", accessToken, body);
|
|
210
|
-
}
|
|
211
|
-
validateTransferOptions(options) {
|
|
212
|
-
const errors = {};
|
|
213
|
-
if (!options.type || !["debit", "credit"].includes(options.type)) {
|
|
214
|
-
errors.type = ['Must be "debit" or "credit"'];
|
|
215
|
-
}
|
|
216
|
-
if (!options.amount) {
|
|
217
|
-
errors.amount = ["Amount is required"];
|
|
218
|
-
} else {
|
|
219
|
-
const amount = parseFloat(options.amount);
|
|
220
|
-
if (isNaN(amount)) {
|
|
221
|
-
errors.amount = ["Must be a valid number"];
|
|
222
|
-
} else if (amount <= 0) {
|
|
223
|
-
errors.amount = ["Must be greater than 0"];
|
|
224
|
-
} else if (!/^\d+(\.\d{1,2})?$/.test(options.amount)) {
|
|
225
|
-
errors.amount = ["Must have at most 2 decimal places"];
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
if (!options.idempotencyKey || options.idempotencyKey.trim() === "") {
|
|
229
|
-
errors.idempotencyKey = ["idempotencyKey is required"];
|
|
230
|
-
} else if (options.idempotencyKey.length > 255) {
|
|
231
|
-
errors.idempotencyKey = ["Must be 255 characters or less"];
|
|
232
|
-
}
|
|
233
|
-
if (Object.keys(errors).length > 0) {
|
|
234
|
-
throw new EdgeValidationError("Invalid transfer options", errors);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
async verifyTransfer(accessToken, transferId, otp) {
|
|
238
|
-
return this.apiRequest(
|
|
239
|
-
"POST",
|
|
240
|
-
`/transfer/${encodeURIComponent(transferId)}/verify`,
|
|
241
|
-
accessToken,
|
|
242
|
-
{ otp }
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
async getTransfer(accessToken, transferId) {
|
|
246
|
-
return this.apiRequest(
|
|
247
|
-
"GET",
|
|
248
|
-
`/transfer/${encodeURIComponent(transferId)}`,
|
|
249
|
-
accessToken
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
async listTransfers(accessToken, params) {
|
|
253
|
-
const queryParams = new URLSearchParams();
|
|
254
|
-
if (params?.limit) queryParams.set("limit", String(params.limit));
|
|
255
|
-
if (params?.offset) queryParams.set("offset", String(params.offset));
|
|
256
|
-
if (params?.status) queryParams.set("status", params.status);
|
|
257
|
-
const query = queryParams.toString();
|
|
258
|
-
const path = query ? `/transfers?${query}` : "/transfers";
|
|
259
|
-
return this.apiRequest("GET", path, accessToken);
|
|
260
|
-
}
|
|
261
|
-
async revokeConsent(accessToken) {
|
|
262
|
-
return this.apiRequest("DELETE", "/consent", accessToken);
|
|
263
|
-
}
|
|
264
|
-
getRetryDelay(attempt) {
|
|
265
|
-
const { backoff, baseDelayMs } = this.retryConfig;
|
|
266
|
-
if (backoff === "exponential") {
|
|
267
|
-
return baseDelayMs * Math.pow(2, attempt);
|
|
268
|
-
}
|
|
269
|
-
return baseDelayMs * (attempt + 1);
|
|
270
|
-
}
|
|
271
|
-
async sleep(ms) {
|
|
272
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
273
|
-
}
|
|
274
|
-
async apiRequest(method, path, accessToken, body) {
|
|
307
|
+
/** @internal Called by {@link EdgeUserClient} — not part of the public API. */
|
|
308
|
+
async _apiRequest(method, path, accessToken, body) {
|
|
275
309
|
const url = `${this.apiBaseUrl}${path}`;
|
|
276
310
|
let lastError = null;
|
|
277
311
|
const mleConfig = this.config.mle;
|
|
@@ -343,7 +377,20 @@ var EdgeConnectServer = class _EdgeConnectServer {
|
|
|
343
377
|
}
|
|
344
378
|
}
|
|
345
379
|
}
|
|
346
|
-
throw new EdgeNetworkError(
|
|
380
|
+
throw new EdgeNetworkError(
|
|
381
|
+
`API request failed after ${this.retryConfig.maxRetries} retries: ${method} ${path}`,
|
|
382
|
+
lastError
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
getRetryDelay(attempt) {
|
|
386
|
+
const { backoff, baseDelayMs } = this.retryConfig;
|
|
387
|
+
if (backoff === "exponential") {
|
|
388
|
+
return baseDelayMs * Math.pow(2, attempt);
|
|
389
|
+
}
|
|
390
|
+
return baseDelayMs * (attempt + 1);
|
|
391
|
+
}
|
|
392
|
+
async sleep(ms) {
|
|
393
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
347
394
|
}
|
|
348
395
|
async fetchWithTimeout(url, options) {
|
|
349
396
|
const controller = new AbortController();
|
|
@@ -371,34 +418,26 @@ var EdgeConnectServer = class _EdgeConnectServer {
|
|
|
371
418
|
const errorCode = error.error;
|
|
372
419
|
const errorMessage = error.message || error.error_description;
|
|
373
420
|
if (errorCode === "invalid_grant" || errorMessage?.includes("Invalid or expired")) {
|
|
374
|
-
return new EdgeTokenExchangeError(
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
);
|
|
421
|
+
return new EdgeTokenExchangeError("Authorization code is invalid, expired, or already used. Please try again.", {
|
|
422
|
+
edgeBoostError: error
|
|
423
|
+
});
|
|
378
424
|
}
|
|
379
425
|
if (errorCode === "invalid_client" || errorMessage?.includes("Invalid client")) {
|
|
380
|
-
return new EdgeAuthenticationError(
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
);
|
|
426
|
+
return new EdgeAuthenticationError("Invalid client credentials. Check your client ID and secret.", {
|
|
427
|
+
edgeBoostError: error
|
|
428
|
+
});
|
|
384
429
|
}
|
|
385
430
|
if (errorMessage?.includes("code_verifier") || errorMessage?.includes("PKCE")) {
|
|
386
|
-
return new EdgeTokenExchangeError(
|
|
387
|
-
"PKCE verification failed. Please try again.",
|
|
388
|
-
{ edgeBoostError: error }
|
|
389
|
-
);
|
|
431
|
+
return new EdgeTokenExchangeError("PKCE verification failed. Please try again.", { edgeBoostError: error });
|
|
390
432
|
}
|
|
391
|
-
return new EdgeTokenExchangeError(
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
);
|
|
433
|
+
return new EdgeTokenExchangeError(errorMessage || "Failed to exchange authorization code", {
|
|
434
|
+
edgeBoostError: error,
|
|
435
|
+
statusCode: status
|
|
436
|
+
});
|
|
395
437
|
}
|
|
396
438
|
async handleApiErrorFromBody(error, status, path) {
|
|
397
439
|
if (status === 401) {
|
|
398
|
-
return new EdgeAuthenticationError(
|
|
399
|
-
error.message || "Access token is invalid or expired",
|
|
400
|
-
error
|
|
401
|
-
);
|
|
440
|
+
return new EdgeAuthenticationError(error.message || "Access token is invalid or expired", error);
|
|
402
441
|
}
|
|
403
442
|
if (status === 403) {
|
|
404
443
|
if (error.error === "consent_required") {
|
|
@@ -420,6 +459,12 @@ var EdgeConnectServer = class _EdgeConnectServer {
|
|
|
420
459
|
const resourceId = path.split("/").pop() || "unknown";
|
|
421
460
|
return new EdgeNotFoundError(resourceType, resourceId);
|
|
422
461
|
}
|
|
462
|
+
if (status === 422 && error.error === "identity_verification_failed") {
|
|
463
|
+
return new EdgeIdentityVerificationError(
|
|
464
|
+
error.fieldErrors || {},
|
|
465
|
+
error.message
|
|
466
|
+
);
|
|
467
|
+
}
|
|
423
468
|
return new EdgeApiError(
|
|
424
469
|
error.error || "api_error",
|
|
425
470
|
error.message || error.error_description || `Request failed with status ${status}`,
|
|
@@ -431,24 +476,21 @@ var EdgeConnectServer = class _EdgeConnectServer {
|
|
|
431
476
|
|
|
432
477
|
// src/index.ts
|
|
433
478
|
import {
|
|
434
|
-
|
|
479
|
+
EdgeApiError as EdgeApiError2,
|
|
435
480
|
EdgeAuthenticationError as EdgeAuthenticationError2,
|
|
436
|
-
EdgeTokenExchangeError as EdgeTokenExchangeError2,
|
|
437
481
|
EdgeConsentRequiredError as EdgeConsentRequiredError2,
|
|
482
|
+
EdgeError as EdgeError2,
|
|
438
483
|
EdgeInsufficientScopeError as EdgeInsufficientScopeError2,
|
|
439
|
-
EdgeApiError as EdgeApiError2,
|
|
440
|
-
EdgeNotFoundError as EdgeNotFoundError2,
|
|
441
484
|
EdgeNetworkError as EdgeNetworkError2,
|
|
442
|
-
|
|
485
|
+
EdgeNotFoundError as EdgeNotFoundError2,
|
|
486
|
+
EdgeTokenExchangeError as EdgeTokenExchangeError2,
|
|
487
|
+
isApiError,
|
|
443
488
|
isAuthenticationError,
|
|
444
489
|
isConsentRequiredError,
|
|
445
|
-
|
|
490
|
+
isEdgeError,
|
|
446
491
|
isNetworkError
|
|
447
492
|
} from "@edge-markets/connect";
|
|
448
|
-
import {
|
|
449
|
-
getEnvironmentConfig as getEnvironmentConfig2,
|
|
450
|
-
isProductionEnvironment
|
|
451
|
-
} from "@edge-markets/connect";
|
|
493
|
+
import { getEnvironmentConfig as getEnvironmentConfig2, isProductionEnvironment } from "@edge-markets/connect";
|
|
452
494
|
export {
|
|
453
495
|
EdgeApiError2 as EdgeApiError,
|
|
454
496
|
EdgeAuthenticationError2 as EdgeAuthenticationError,
|
|
@@ -459,6 +501,7 @@ export {
|
|
|
459
501
|
EdgeNetworkError2 as EdgeNetworkError,
|
|
460
502
|
EdgeNotFoundError2 as EdgeNotFoundError,
|
|
461
503
|
EdgeTokenExchangeError2 as EdgeTokenExchangeError,
|
|
504
|
+
EdgeUserClient,
|
|
462
505
|
getEnvironmentConfig2 as getEnvironmentConfig,
|
|
463
506
|
isApiError,
|
|
464
507
|
isAuthenticationError,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@edge-markets/connect-node",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Server SDK for EDGE Connect token exchange and API calls",
|
|
5
5
|
"author": "Edge Markets",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@edge-markets/connect": "^1.
|
|
24
|
+
"@edge-markets/connect": "^1.3.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"tsup": "^8.0.0",
|