@alter-ai/alter-sdk 0.2.2 → 0.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/README.md +132 -51
- package/dist/index.cjs +543 -80
- package/dist/index.d.cts +100 -18
- package/dist/index.d.ts +100 -18
- package/dist/index.js +529 -80
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,15 +17,27 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
21
31
|
var index_exports = {};
|
|
22
32
|
__export(index_exports, {
|
|
23
33
|
APICallAuditLog: () => APICallAuditLog,
|
|
34
|
+
ActorType: () => ActorType,
|
|
24
35
|
AlterSDKError: () => AlterSDKError,
|
|
25
36
|
AlterVault: () => AlterVault,
|
|
37
|
+
ConnectFlowError: () => ConnectFlowError,
|
|
38
|
+
ConnectResult: () => ConnectResult,
|
|
26
39
|
ConnectSession: () => ConnectSession,
|
|
40
|
+
ConnectTimeoutError: () => ConnectTimeoutError,
|
|
27
41
|
ConnectionInfo: () => ConnectionInfo,
|
|
28
42
|
ConnectionListResult: () => ConnectionListResult,
|
|
29
43
|
ConnectionNotFoundError: () => ConnectionNotFoundError,
|
|
@@ -39,6 +53,9 @@ __export(index_exports, {
|
|
|
39
53
|
});
|
|
40
54
|
module.exports = __toCommonJS(index_exports);
|
|
41
55
|
|
|
56
|
+
// src/client.ts
|
|
57
|
+
var import_node_crypto2 = require("crypto");
|
|
58
|
+
|
|
42
59
|
// src/exceptions.ts
|
|
43
60
|
var AlterSDKError = class extends Error {
|
|
44
61
|
details;
|
|
@@ -83,6 +100,18 @@ var TokenExpiredError = class extends TokenRetrievalError {
|
|
|
83
100
|
this.connectionId = connectionId;
|
|
84
101
|
}
|
|
85
102
|
};
|
|
103
|
+
var ConnectFlowError = class extends AlterSDKError {
|
|
104
|
+
constructor(message, details) {
|
|
105
|
+
super(message, details);
|
|
106
|
+
this.name = "ConnectFlowError";
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
var ConnectTimeoutError = class extends ConnectFlowError {
|
|
110
|
+
constructor(message, details) {
|
|
111
|
+
super(message, details);
|
|
112
|
+
this.name = "ConnectTimeoutError";
|
|
113
|
+
}
|
|
114
|
+
};
|
|
86
115
|
var ProviderAPIError = class extends AlterSDKError {
|
|
87
116
|
statusCode;
|
|
88
117
|
responseBody;
|
|
@@ -107,6 +136,12 @@ var TimeoutError = class extends NetworkError {
|
|
|
107
136
|
};
|
|
108
137
|
|
|
109
138
|
// src/models.ts
|
|
139
|
+
var ActorType = /* @__PURE__ */ ((ActorType2) => {
|
|
140
|
+
ActorType2["BACKEND_SERVICE"] = "backend_service";
|
|
141
|
+
ActorType2["AI_AGENT"] = "ai_agent";
|
|
142
|
+
ActorType2["MCP_SERVER"] = "mcp_server";
|
|
143
|
+
return ActorType2;
|
|
144
|
+
})(ActorType || {});
|
|
110
145
|
var TokenResponse = class _TokenResponse {
|
|
111
146
|
/** Token type (usually "Bearer") */
|
|
112
147
|
tokenType;
|
|
@@ -118,12 +153,29 @@ var TokenResponse = class _TokenResponse {
|
|
|
118
153
|
scopes;
|
|
119
154
|
/** Connection ID that provided this token */
|
|
120
155
|
connectionId;
|
|
156
|
+
/** Provider ID (google, github, etc.) */
|
|
157
|
+
providerId;
|
|
158
|
+
/** HTTP header name for credential injection (e.g., "Authorization", "X-API-Key") */
|
|
159
|
+
injectionHeader;
|
|
160
|
+
/** Header value format with {token} placeholder (e.g., "Bearer {token}", "{token}") */
|
|
161
|
+
injectionFormat;
|
|
162
|
+
/** Extra credentials for multi-part auth (e.g. AWS SigV4 secret_key, region) */
|
|
163
|
+
additionalCredentials;
|
|
121
164
|
constructor(data) {
|
|
122
165
|
this.tokenType = data.token_type ?? "Bearer";
|
|
123
166
|
this.expiresIn = data.expires_in ?? null;
|
|
124
167
|
this.expiresAt = data.expires_at ? _TokenResponse.parseExpiresAt(data.expires_at) : null;
|
|
125
168
|
this.scopes = data.scopes ?? [];
|
|
126
169
|
this.connectionId = data.connection_id;
|
|
170
|
+
this.providerId = data.provider_id ?? "";
|
|
171
|
+
this.injectionHeader = data.injection_header ?? "Authorization";
|
|
172
|
+
this.injectionFormat = data.injection_format ?? "Bearer {token}";
|
|
173
|
+
if (data.additional_credentials) {
|
|
174
|
+
const { secret_key: _, ...safeCredentials } = data.additional_credentials;
|
|
175
|
+
this.additionalCredentials = Object.keys(safeCredentials).length > 0 ? safeCredentials : null;
|
|
176
|
+
} else {
|
|
177
|
+
this.additionalCredentials = null;
|
|
178
|
+
}
|
|
127
179
|
Object.freeze(this);
|
|
128
180
|
}
|
|
129
181
|
/**
|
|
@@ -188,7 +240,6 @@ var TokenResponse = class _TokenResponse {
|
|
|
188
240
|
var ConnectionInfo = class {
|
|
189
241
|
id;
|
|
190
242
|
providerId;
|
|
191
|
-
attributes;
|
|
192
243
|
scopes;
|
|
193
244
|
accountIdentifier;
|
|
194
245
|
accountDisplayName;
|
|
@@ -199,7 +250,6 @@ var ConnectionInfo = class {
|
|
|
199
250
|
constructor(data) {
|
|
200
251
|
this.id = data.id;
|
|
201
252
|
this.providerId = data.provider_id;
|
|
202
|
-
this.attributes = data.attributes ?? {};
|
|
203
253
|
this.scopes = data.scopes ?? [];
|
|
204
254
|
this.accountIdentifier = data.account_identifier ?? null;
|
|
205
255
|
this.accountDisplayName = data.account_display_name ?? null;
|
|
@@ -213,7 +263,6 @@ var ConnectionInfo = class {
|
|
|
213
263
|
return {
|
|
214
264
|
id: this.id,
|
|
215
265
|
provider_id: this.providerId,
|
|
216
|
-
attributes: this.attributes,
|
|
217
266
|
scopes: this.scopes,
|
|
218
267
|
account_identifier: this.accountIdentifier,
|
|
219
268
|
account_display_name: this.accountDisplayName,
|
|
@@ -266,12 +315,38 @@ var ConnectionListResult = class {
|
|
|
266
315
|
Object.freeze(this);
|
|
267
316
|
}
|
|
268
317
|
};
|
|
318
|
+
var ConnectResult = class {
|
|
319
|
+
connectionId;
|
|
320
|
+
providerId;
|
|
321
|
+
accountIdentifier;
|
|
322
|
+
scopes;
|
|
323
|
+
constructor(data) {
|
|
324
|
+
this.connectionId = data.connection_id;
|
|
325
|
+
this.providerId = data.provider_id;
|
|
326
|
+
this.accountIdentifier = data.account_identifier ?? null;
|
|
327
|
+
this.scopes = data.scopes ?? [];
|
|
328
|
+
Object.freeze(this);
|
|
329
|
+
}
|
|
330
|
+
toJSON() {
|
|
331
|
+
return {
|
|
332
|
+
connection_id: this.connectionId,
|
|
333
|
+
provider_id: this.providerId,
|
|
334
|
+
account_identifier: this.accountIdentifier,
|
|
335
|
+
scopes: this.scopes
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
toString() {
|
|
339
|
+
return `ConnectResult(connection_id=${this.connectionId}, provider=${this.providerId})`;
|
|
340
|
+
}
|
|
341
|
+
};
|
|
269
342
|
var SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
|
|
270
343
|
"authorization",
|
|
271
344
|
"cookie",
|
|
272
345
|
"set-cookie",
|
|
273
346
|
"x-api-key",
|
|
274
|
-
"x-auth-token"
|
|
347
|
+
"x-auth-token",
|
|
348
|
+
"x-amz-date",
|
|
349
|
+
"x-amz-content-sha256"
|
|
275
350
|
]);
|
|
276
351
|
var APICallAuditLog = class {
|
|
277
352
|
connectionId;
|
|
@@ -345,11 +420,171 @@ var APICallAuditLog = class {
|
|
|
345
420
|
}
|
|
346
421
|
};
|
|
347
422
|
|
|
423
|
+
// src/aws-sig-v4.ts
|
|
424
|
+
var import_node_crypto = require("crypto");
|
|
425
|
+
var ALGORITHM = "AWS4-HMAC-SHA256";
|
|
426
|
+
var AWS_HOST_RE = /^(?<service>[a-z0-9-]+)\.(?<region>[a-z]{2}(?:-[a-z0-9]+)+-\d+)\.amazonaws\.com$/;
|
|
427
|
+
var S3_VIRTUAL_HOST_RE = /^[^.]+\.s3\.(?<region>[a-z]{2}(?:-[a-z0-9]+)+-\d+)\.amazonaws\.com$/;
|
|
428
|
+
function hmacSha256(key, message) {
|
|
429
|
+
return (0, import_node_crypto.createHmac)("sha256", key).update(message, "utf8").digest();
|
|
430
|
+
}
|
|
431
|
+
function sha256Hex(data) {
|
|
432
|
+
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
433
|
+
if (typeof data === "string") {
|
|
434
|
+
hash.update(data, "utf8");
|
|
435
|
+
} else {
|
|
436
|
+
hash.update(data);
|
|
437
|
+
}
|
|
438
|
+
return hash.digest("hex");
|
|
439
|
+
}
|
|
440
|
+
function detectServiceAndRegion(hostname) {
|
|
441
|
+
const lower = hostname.toLowerCase();
|
|
442
|
+
const m = AWS_HOST_RE.exec(lower);
|
|
443
|
+
if (m?.groups) {
|
|
444
|
+
return { service: m.groups.service, region: m.groups.region };
|
|
445
|
+
}
|
|
446
|
+
const s3m = S3_VIRTUAL_HOST_RE.exec(lower);
|
|
447
|
+
if (s3m?.groups) {
|
|
448
|
+
return { service: "s3", region: s3m.groups.region };
|
|
449
|
+
}
|
|
450
|
+
return { service: null, region: null };
|
|
451
|
+
}
|
|
452
|
+
function deriveSigningKey(secretKey, dateStamp, region, service) {
|
|
453
|
+
const kDate = hmacSha256(Buffer.from("AWS4" + secretKey, "utf8"), dateStamp);
|
|
454
|
+
const kRegion = hmacSha256(kDate, region);
|
|
455
|
+
const kService = hmacSha256(kRegion, service);
|
|
456
|
+
const kSigning = hmacSha256(kService, "aws4_request");
|
|
457
|
+
return kSigning;
|
|
458
|
+
}
|
|
459
|
+
function canonicalUri(path) {
|
|
460
|
+
if (!path) return "/";
|
|
461
|
+
if (!path.startsWith("/")) path = "/" + path;
|
|
462
|
+
const segments = path.split("/");
|
|
463
|
+
return segments.map(
|
|
464
|
+
(seg) => encodeURIComponent(seg).replace(
|
|
465
|
+
/[!'()*]/g,
|
|
466
|
+
(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
|
|
467
|
+
)
|
|
468
|
+
).join("/");
|
|
469
|
+
}
|
|
470
|
+
function canonicalQueryString(query) {
|
|
471
|
+
if (!query) return "";
|
|
472
|
+
const sorted = [];
|
|
473
|
+
for (const pair of query.split("&")) {
|
|
474
|
+
const eqIdx = pair.indexOf("=");
|
|
475
|
+
if (eqIdx === -1) {
|
|
476
|
+
sorted.push([decodeURIComponent(pair), ""]);
|
|
477
|
+
} else {
|
|
478
|
+
sorted.push([
|
|
479
|
+
decodeURIComponent(pair.slice(0, eqIdx)),
|
|
480
|
+
decodeURIComponent(pair.slice(eqIdx + 1))
|
|
481
|
+
]);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
sorted.sort((a, b) => {
|
|
485
|
+
if (a[0] < b[0]) return -1;
|
|
486
|
+
if (a[0] > b[0]) return 1;
|
|
487
|
+
if (a[1] < b[1]) return -1;
|
|
488
|
+
if (a[1] > b[1]) return 1;
|
|
489
|
+
return 0;
|
|
490
|
+
});
|
|
491
|
+
const sigv4Encode = (s) => encodeURIComponent(s).replace(
|
|
492
|
+
/[!'()*]/g,
|
|
493
|
+
(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
|
|
494
|
+
);
|
|
495
|
+
return sorted.map(([k, v]) => `${sigv4Encode(k)}=${sigv4Encode(v)}`).join("&");
|
|
496
|
+
}
|
|
497
|
+
function canonicalHeadersAndSigned(headers) {
|
|
498
|
+
const canonical = {};
|
|
499
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
500
|
+
const lowerName = name.toLowerCase().trim();
|
|
501
|
+
const trimmedValue = value.replace(/\s+/g, " ").trim();
|
|
502
|
+
canonical[lowerName] = trimmedValue;
|
|
503
|
+
}
|
|
504
|
+
const sortedNames = Object.keys(canonical).sort();
|
|
505
|
+
const canonicalStr = sortedNames.map((name) => `${name}:${canonical[name]}
|
|
506
|
+
`).join("");
|
|
507
|
+
const signedStr = sortedNames.join(";");
|
|
508
|
+
return { canonicalHeaders: canonicalStr, signedHeaders: signedStr };
|
|
509
|
+
}
|
|
510
|
+
function signAwsRequest(opts) {
|
|
511
|
+
const parsed = new URL(opts.url);
|
|
512
|
+
const hostname = parsed.hostname;
|
|
513
|
+
let { region, service } = opts;
|
|
514
|
+
if (region == null || service == null) {
|
|
515
|
+
const detected = detectServiceAndRegion(hostname);
|
|
516
|
+
if (region == null) region = detected.region;
|
|
517
|
+
if (service == null) service = detected.service;
|
|
518
|
+
}
|
|
519
|
+
if (!region) {
|
|
520
|
+
throw new Error(
|
|
521
|
+
`Cannot determine AWS region from URL '${opts.url}'. Pass region explicitly via additional_credentials.`
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
if (!service) {
|
|
525
|
+
throw new Error(
|
|
526
|
+
`Cannot determine AWS service from URL '${opts.url}'. Pass service explicitly via additional_credentials.`
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
const timestamp = opts.timestamp ?? /* @__PURE__ */ new Date();
|
|
530
|
+
const amzDate = timestamp.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
531
|
+
const dateStamp = amzDate.slice(0, 8);
|
|
532
|
+
const bodyBytes = opts.body != null ? typeof opts.body === "string" ? Buffer.from(opts.body, "utf8") : opts.body : Buffer.alloc(0);
|
|
533
|
+
const payloadHash = sha256Hex(bodyBytes);
|
|
534
|
+
const headersToSign = {};
|
|
535
|
+
const hasHost = Object.keys(opts.headers).some(
|
|
536
|
+
(k) => k.toLowerCase() === "host"
|
|
537
|
+
);
|
|
538
|
+
if (!hasHost) {
|
|
539
|
+
const port = parsed.port;
|
|
540
|
+
headersToSign["host"] = port && port !== "80" && port !== "443" ? `${hostname}:${port}` : hostname;
|
|
541
|
+
} else {
|
|
542
|
+
for (const [k, v] of Object.entries(opts.headers)) {
|
|
543
|
+
if (k.toLowerCase() === "host") {
|
|
544
|
+
headersToSign["host"] = v;
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
headersToSign["x-amz-date"] = amzDate;
|
|
550
|
+
headersToSign["x-amz-content-sha256"] = payloadHash;
|
|
551
|
+
const canonicalUriStr = canonicalUri(parsed.pathname);
|
|
552
|
+
const canonicalQs = canonicalQueryString(parsed.search.replace(/^\?/, ""));
|
|
553
|
+
const { canonicalHeaders, signedHeaders } = canonicalHeadersAndSigned(headersToSign);
|
|
554
|
+
const canonicalRequest = [
|
|
555
|
+
opts.method.toUpperCase(),
|
|
556
|
+
canonicalUriStr,
|
|
557
|
+
canonicalQs,
|
|
558
|
+
canonicalHeaders,
|
|
559
|
+
signedHeaders,
|
|
560
|
+
payloadHash
|
|
561
|
+
].join("\n");
|
|
562
|
+
const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
|
|
563
|
+
const stringToSign = [
|
|
564
|
+
ALGORITHM,
|
|
565
|
+
amzDate,
|
|
566
|
+
credentialScope,
|
|
567
|
+
sha256Hex(canonicalRequest)
|
|
568
|
+
].join("\n");
|
|
569
|
+
const signingKey = deriveSigningKey(opts.secretKey, dateStamp, region, service);
|
|
570
|
+
const signature = (0, import_node_crypto.createHmac)("sha256", signingKey).update(stringToSign, "utf8").digest("hex");
|
|
571
|
+
const authorization = `${ALGORITHM} Credential=${opts.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
572
|
+
return {
|
|
573
|
+
Authorization: authorization,
|
|
574
|
+
"x-amz-date": amzDate,
|
|
575
|
+
"x-amz-content-sha256": payloadHash
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
348
579
|
// src/client.ts
|
|
349
580
|
var _tokenStore = /* @__PURE__ */ new WeakMap();
|
|
581
|
+
var _additionalCredsStore = /* @__PURE__ */ new WeakMap();
|
|
350
582
|
function _storeAccessToken(token, accessToken) {
|
|
351
583
|
_tokenStore.set(token, accessToken);
|
|
352
584
|
}
|
|
585
|
+
function _storeAdditionalCredentials(token, creds) {
|
|
586
|
+
_additionalCredsStore.set(token, creds);
|
|
587
|
+
}
|
|
353
588
|
function _extractAccessToken(token) {
|
|
354
589
|
const value = _tokenStore.get(token);
|
|
355
590
|
if (value === void 0) {
|
|
@@ -359,10 +594,12 @@ function _extractAccessToken(token) {
|
|
|
359
594
|
}
|
|
360
595
|
return value;
|
|
361
596
|
}
|
|
597
|
+
function _extractAdditionalCredentials(token) {
|
|
598
|
+
return _additionalCredsStore.get(token);
|
|
599
|
+
}
|
|
362
600
|
var _fetch;
|
|
363
|
-
var SDK_VERSION = "0.
|
|
601
|
+
var SDK_VERSION = "0.4.0";
|
|
364
602
|
var SDK_USER_AGENT = `alter-sdk-node/${SDK_VERSION}`;
|
|
365
|
-
var VALID_ACTOR_TYPES = ["ai_agent", "mcp_server"];
|
|
366
603
|
var HTTP_FORBIDDEN = 403;
|
|
367
604
|
var HTTP_NOT_FOUND = 404;
|
|
368
605
|
var HTTP_BAD_REQUEST = 400;
|
|
@@ -418,7 +655,9 @@ var HttpClient = class {
|
|
|
418
655
|
headers: mergedHeaders,
|
|
419
656
|
signal: controller.signal
|
|
420
657
|
};
|
|
421
|
-
if (options?.
|
|
658
|
+
if (options?.body !== void 0) {
|
|
659
|
+
init.body = options.body;
|
|
660
|
+
} else if (options?.json !== void 0) {
|
|
422
661
|
init.body = JSON.stringify(options.json);
|
|
423
662
|
mergedHeaders["Content-Type"] = "application/json";
|
|
424
663
|
}
|
|
@@ -442,6 +681,8 @@ var AlterVault = class _AlterVault {
|
|
|
442
681
|
// SECURITY LAYER 4: ES2022 private fields — truly private at runtime.
|
|
443
682
|
// These are NOT accessible via (obj as any), Object.keys(), or prototype.
|
|
444
683
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
684
|
+
/** HMAC signing key (derived from API key using AWS SigV4 pattern, raw bytes) */
|
|
685
|
+
#hmacKey;
|
|
445
686
|
/** HTTP Client for Alter Backend (has x-api-key) */
|
|
446
687
|
#alterClient;
|
|
447
688
|
/** HTTP Client for External Provider APIs (NO x-api-key) */
|
|
@@ -479,10 +720,8 @@ var AlterVault = class _AlterVault {
|
|
|
479
720
|
for (const [name, value] of actorStrings) {
|
|
480
721
|
_AlterVault.#validateActorString(value, name);
|
|
481
722
|
}
|
|
482
|
-
this
|
|
483
|
-
|
|
484
|
-
""
|
|
485
|
-
);
|
|
723
|
+
this.#hmacKey = (0, import_node_crypto2.createHmac)("sha256", options.apiKey).update("alter-signing-v1").digest();
|
|
724
|
+
this.baseUrl = (process.env.ALTER_BASE_URL ?? "https://backend.alterai.dev").replace(/\/+$/, "");
|
|
486
725
|
const timeoutMs = options.timeout ?? 3e4;
|
|
487
726
|
this.#actorType = options.actorType;
|
|
488
727
|
this.#actorIdentifier = options.actorIdentifier;
|
|
@@ -535,14 +774,20 @@ var AlterVault = class _AlterVault {
|
|
|
535
774
|
if (!apiKey.startsWith("alter_key_")) {
|
|
536
775
|
throw new AlterSDKError("api_key must start with 'alter_key_'");
|
|
537
776
|
}
|
|
538
|
-
if (
|
|
539
|
-
throw new AlterSDKError(
|
|
777
|
+
if (!actorType) {
|
|
778
|
+
throw new AlterSDKError(
|
|
779
|
+
"actor_type is required (use ActorType.AI_AGENT, ActorType.MCP_SERVER, or ActorType.BACKEND_SERVICE)"
|
|
780
|
+
);
|
|
540
781
|
}
|
|
541
|
-
|
|
782
|
+
const validValues = Object.values(ActorType);
|
|
783
|
+
if (!validValues.includes(String(actorType))) {
|
|
542
784
|
throw new AlterSDKError(
|
|
543
|
-
|
|
785
|
+
`actor_type must be one of ${JSON.stringify(validValues.sort())}, got '${String(actorType)}'`
|
|
544
786
|
);
|
|
545
787
|
}
|
|
788
|
+
if (!actorIdentifier) {
|
|
789
|
+
throw new AlterSDKError("actor_identifier is required");
|
|
790
|
+
}
|
|
546
791
|
}
|
|
547
792
|
/**
|
|
548
793
|
* Build default headers for the Alter backend HTTP client.
|
|
@@ -569,6 +814,28 @@ var AlterVault = class _AlterVault {
|
|
|
569
814
|
}
|
|
570
815
|
return headers;
|
|
571
816
|
}
|
|
817
|
+
/**
|
|
818
|
+
* Compute HMAC-SHA256 signature headers for an Alter backend request.
|
|
819
|
+
*
|
|
820
|
+
* String-to-sign format: METHOD\nPATH_WITH_SORTED_QUERY\nTIMESTAMP\nCONTENT_HASH
|
|
821
|
+
*
|
|
822
|
+
* The path should include sorted query parameters if present (e.g. "/sdk/endpoint?a=1&b=2").
|
|
823
|
+
* Currently all SDK→backend calls are POSTs without query params, so the path is clean.
|
|
824
|
+
*/
|
|
825
|
+
#computeHmacHeaders(method, path, body) {
|
|
826
|
+
const timestamp = String(Math.floor(Date.now() / 1e3));
|
|
827
|
+
const contentHash = (0, import_node_crypto2.createHash)("sha256").update(body ?? "").digest("hex");
|
|
828
|
+
const stringToSign = `${method.toUpperCase()}
|
|
829
|
+
${path}
|
|
830
|
+
${timestamp}
|
|
831
|
+
${contentHash}`;
|
|
832
|
+
const signature = (0, import_node_crypto2.createHmac)("sha256", this.#hmacKey).update(stringToSign).digest("hex");
|
|
833
|
+
return {
|
|
834
|
+
"X-Alter-Timestamp": timestamp,
|
|
835
|
+
"X-Alter-Content-SHA256": contentHash,
|
|
836
|
+
"X-Alter-Signature": signature
|
|
837
|
+
};
|
|
838
|
+
}
|
|
572
839
|
/**
|
|
573
840
|
* Build per-request actor headers for instance tracking.
|
|
574
841
|
*/
|
|
@@ -665,7 +932,7 @@ var AlterVault = class _AlterVault {
|
|
|
665
932
|
if (response.status === HTTP_NOT_FOUND) {
|
|
666
933
|
const errorData = await _AlterVault.#safeParseJson(response);
|
|
667
934
|
throw new ConnectionNotFoundError(
|
|
668
|
-
errorData.message ?? "OAuth connection not found for
|
|
935
|
+
errorData.message ?? "OAuth connection not found for the given connection_id",
|
|
669
936
|
errorData
|
|
670
937
|
);
|
|
671
938
|
}
|
|
@@ -711,22 +978,24 @@ var AlterVault = class _AlterVault {
|
|
|
711
978
|
* This is a private method. Tokens are NEVER exposed to developers.
|
|
712
979
|
* Use request() instead, which handles tokens internally.
|
|
713
980
|
*/
|
|
714
|
-
async #getToken(
|
|
981
|
+
async #getToken(connectionId, reason, requestMetadata, runId, threadId, toolCallId) {
|
|
715
982
|
const actorHeaders = this.#getActorRequestHeaders(
|
|
716
983
|
runId,
|
|
717
984
|
threadId,
|
|
718
985
|
toolCallId
|
|
719
986
|
);
|
|
720
987
|
let response;
|
|
988
|
+
const tokenBody = {
|
|
989
|
+
connection_id: connectionId,
|
|
990
|
+
reason: reason ?? null,
|
|
991
|
+
request: requestMetadata ?? null
|
|
992
|
+
};
|
|
993
|
+
const tokenPath = "/sdk/token";
|
|
994
|
+
const hmacHeaders = this.#computeHmacHeaders("POST", tokenPath, JSON.stringify(tokenBody));
|
|
721
995
|
try {
|
|
722
|
-
response = await this.#alterClient.post(
|
|
723
|
-
json:
|
|
724
|
-
|
|
725
|
-
attributes,
|
|
726
|
-
reason: reason ?? null,
|
|
727
|
-
request: requestMetadata ?? null
|
|
728
|
-
},
|
|
729
|
-
headers: actorHeaders
|
|
996
|
+
response = await this.#alterClient.post(tokenPath, {
|
|
997
|
+
json: tokenBody,
|
|
998
|
+
headers: { ...actorHeaders, ...hmacHeaders }
|
|
730
999
|
});
|
|
731
1000
|
} catch (error) {
|
|
732
1001
|
if (_AlterVault.#isTimeoutOrAbortError(error)) {
|
|
@@ -743,7 +1012,7 @@ var AlterVault = class _AlterVault {
|
|
|
743
1012
|
}
|
|
744
1013
|
throw new TokenRetrievalError(
|
|
745
1014
|
`Failed to retrieve token: ${error instanceof Error ? error.message : String(error)}`,
|
|
746
|
-
{
|
|
1015
|
+
{ connection_id: connectionId, error: String(error) }
|
|
747
1016
|
);
|
|
748
1017
|
}
|
|
749
1018
|
this.#cacheActorIdFromResponse(response);
|
|
@@ -751,7 +1020,22 @@ var AlterVault = class _AlterVault {
|
|
|
751
1020
|
const tokenData = await response.json();
|
|
752
1021
|
const typedData = tokenData;
|
|
753
1022
|
const tokenResponse = new TokenResponse(typedData);
|
|
1023
|
+
if (!/^[A-Za-z][A-Za-z0-9-]*$/.test(tokenResponse.injectionHeader)) {
|
|
1024
|
+
throw new TokenRetrievalError(
|
|
1025
|
+
`Backend returned invalid injection_header: ${tokenResponse.injectionHeader}`,
|
|
1026
|
+
{ connectionId: String(connectionId) }
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
if (/[\r\n\x00]/.test(tokenResponse.injectionFormat)) {
|
|
1030
|
+
throw new TokenRetrievalError(
|
|
1031
|
+
`Backend returned invalid injection_format (contains control characters)`,
|
|
1032
|
+
{ connectionId: String(connectionId) }
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
754
1035
|
_storeAccessToken(tokenResponse, typedData.access_token);
|
|
1036
|
+
if (typedData.additional_credentials) {
|
|
1037
|
+
_storeAdditionalCredentials(tokenResponse, typedData.additional_credentials);
|
|
1038
|
+
}
|
|
755
1039
|
return tokenResponse;
|
|
756
1040
|
}
|
|
757
1041
|
/**
|
|
@@ -779,10 +1063,13 @@ var AlterVault = class _AlterVault {
|
|
|
779
1063
|
toolCallId: params.toolCallId
|
|
780
1064
|
});
|
|
781
1065
|
const sanitized = auditLog.sanitize();
|
|
782
|
-
const actorHeaders = this.#getActorRequestHeaders();
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
|
|
1066
|
+
const actorHeaders = this.#getActorRequestHeaders(params.runId);
|
|
1067
|
+
const auditPath = "/sdk/oauth/audit/api-call";
|
|
1068
|
+
const auditBody = sanitized;
|
|
1069
|
+
const auditHmac = this.#computeHmacHeaders("POST", auditPath, JSON.stringify(auditBody));
|
|
1070
|
+
const response = await this.#alterClient.post(auditPath, {
|
|
1071
|
+
json: auditBody,
|
|
1072
|
+
headers: { ...actorHeaders, ...auditHmac }
|
|
786
1073
|
});
|
|
787
1074
|
this.#cacheActorIdFromResponse(response);
|
|
788
1075
|
if (!response.ok) {
|
|
@@ -848,13 +1135,13 @@ var AlterVault = class _AlterVault {
|
|
|
848
1135
|
* 4. Logs the call for audit (fire-and-forget)
|
|
849
1136
|
* 5. Returns the raw response
|
|
850
1137
|
*/
|
|
851
|
-
async request(
|
|
1138
|
+
async request(connectionId, method, url, options) {
|
|
852
1139
|
if (this.#closed) {
|
|
853
1140
|
throw new AlterSDKError(
|
|
854
1141
|
"SDK instance has been closed. Create a new AlterVault instance to make requests."
|
|
855
1142
|
);
|
|
856
1143
|
}
|
|
857
|
-
const
|
|
1144
|
+
const runId = options?.runId ?? (0, import_node_crypto2.randomUUID)();
|
|
858
1145
|
const methodStr = String(method).toUpperCase();
|
|
859
1146
|
const urlLower = url.toLowerCase();
|
|
860
1147
|
if (!ALLOWED_URL_SCHEMES.some((scheme) => urlLower.startsWith(scheme))) {
|
|
@@ -862,7 +1149,7 @@ var AlterVault = class _AlterVault {
|
|
|
862
1149
|
`URL must start with https:// or http://, got: ${url.slice(0, 50)}`
|
|
863
1150
|
);
|
|
864
1151
|
}
|
|
865
|
-
if (options
|
|
1152
|
+
if (options?.pathParams && Object.keys(options.pathParams).length > 0) {
|
|
866
1153
|
const encodedParams = {};
|
|
867
1154
|
for (const [key, value] of Object.entries(options.pathParams)) {
|
|
868
1155
|
encodedParams[key] = encodeURIComponent(String(value));
|
|
@@ -892,39 +1179,80 @@ var AlterVault = class _AlterVault {
|
|
|
892
1179
|
);
|
|
893
1180
|
}
|
|
894
1181
|
}
|
|
895
|
-
if (options.extraHeaders && "Authorization" in options.extraHeaders) {
|
|
896
|
-
console.warn(
|
|
897
|
-
"extraHeaders contains 'Authorization' which will be overwritten with the auto-injected Bearer token"
|
|
898
|
-
);
|
|
899
|
-
}
|
|
900
1182
|
const tokenResponse = await this.#getToken(
|
|
901
|
-
|
|
902
|
-
options
|
|
903
|
-
options.reason,
|
|
1183
|
+
connectionId,
|
|
1184
|
+
options?.reason,
|
|
904
1185
|
{ method: methodStr, url },
|
|
905
|
-
|
|
906
|
-
options
|
|
907
|
-
options
|
|
1186
|
+
runId,
|
|
1187
|
+
options?.threadId,
|
|
1188
|
+
options?.toolCallId
|
|
908
1189
|
);
|
|
909
|
-
const requestHeaders = options
|
|
910
|
-
|
|
1190
|
+
const requestHeaders = options?.extraHeaders ? { ...options.extraHeaders } : {};
|
|
1191
|
+
const accessToken = _extractAccessToken(tokenResponse);
|
|
1192
|
+
const injectionHeaderLower = tokenResponse.injectionHeader.toLowerCase();
|
|
1193
|
+
const additionalCreds = _extractAdditionalCredentials(tokenResponse);
|
|
1194
|
+
const isSigV4 = tokenResponse.injectionFormat.startsWith("AWS4-HMAC-SHA256") && additionalCreds != null;
|
|
1195
|
+
let sigv4BodyStr = null;
|
|
1196
|
+
if (isSigV4) {
|
|
1197
|
+
const accessKeyId = accessToken;
|
|
1198
|
+
if (options?.json != null) {
|
|
1199
|
+
sigv4BodyStr = JSON.stringify(options.json);
|
|
1200
|
+
if (!requestHeaders["Content-Type"]) {
|
|
1201
|
+
requestHeaders["Content-Type"] = "application/json";
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
if (!additionalCreds.secret_key) {
|
|
1205
|
+
throw new TokenRetrievalError(
|
|
1206
|
+
"AWS SigV4 credential is missing secret_key in additional_credentials. Re-store the credential with both Access Key ID and Secret Access Key.",
|
|
1207
|
+
{ connection_id: connectionId }
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
1210
|
+
const awsHeaders = signAwsRequest({
|
|
1211
|
+
method: methodStr,
|
|
1212
|
+
url,
|
|
1213
|
+
headers: requestHeaders,
|
|
1214
|
+
body: sigv4BodyStr,
|
|
1215
|
+
accessKeyId,
|
|
1216
|
+
secretKey: additionalCreds.secret_key,
|
|
1217
|
+
region: additionalCreds.region ?? null,
|
|
1218
|
+
service: additionalCreds.service ?? null
|
|
1219
|
+
});
|
|
1220
|
+
Object.assign(requestHeaders, awsHeaders);
|
|
1221
|
+
} else {
|
|
1222
|
+
if (options?.extraHeaders && Object.keys(options.extraHeaders).some(
|
|
1223
|
+
(k) => k.toLowerCase() === injectionHeaderLower
|
|
1224
|
+
)) {
|
|
1225
|
+
console.warn(
|
|
1226
|
+
`extraHeaders contains '${tokenResponse.injectionHeader}' which will be overwritten with the auto-injected credential`
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
requestHeaders[tokenResponse.injectionHeader] = tokenResponse.injectionFormat.replace("{token}", accessToken);
|
|
1230
|
+
}
|
|
911
1231
|
if (!requestHeaders["User-Agent"]) {
|
|
912
1232
|
requestHeaders["User-Agent"] = SDK_USER_AGENT;
|
|
913
1233
|
}
|
|
914
1234
|
const startTime = Date.now();
|
|
915
1235
|
let response;
|
|
916
1236
|
try {
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1237
|
+
if (isSigV4 && sigv4BodyStr != null) {
|
|
1238
|
+
response = await this.#providerClient.request(methodStr, url, {
|
|
1239
|
+
body: sigv4BodyStr,
|
|
1240
|
+
headers: requestHeaders,
|
|
1241
|
+
params: options?.queryParams
|
|
1242
|
+
});
|
|
1243
|
+
} else {
|
|
1244
|
+
response = await this.#providerClient.request(methodStr, url, {
|
|
1245
|
+
json: options?.json,
|
|
1246
|
+
headers: requestHeaders,
|
|
1247
|
+
params: options?.queryParams
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
922
1250
|
} catch (error) {
|
|
923
1251
|
if (_AlterVault.#isTimeoutOrAbortError(error)) {
|
|
924
1252
|
throw new TimeoutError(
|
|
925
1253
|
`Provider API request timed out: ${error instanceof Error ? error.message : String(error)}`,
|
|
926
1254
|
{
|
|
927
|
-
|
|
1255
|
+
connection_id: connectionId,
|
|
928
1256
|
method: methodStr,
|
|
929
1257
|
url
|
|
930
1258
|
}
|
|
@@ -933,7 +1261,7 @@ var AlterVault = class _AlterVault {
|
|
|
933
1261
|
throw new NetworkError(
|
|
934
1262
|
`Failed to call provider API: ${error instanceof Error ? error.message : String(error)}`,
|
|
935
1263
|
{
|
|
936
|
-
|
|
1264
|
+
connection_id: connectionId,
|
|
937
1265
|
method: methodStr,
|
|
938
1266
|
url,
|
|
939
1267
|
error: String(error)
|
|
@@ -941,9 +1269,11 @@ var AlterVault = class _AlterVault {
|
|
|
941
1269
|
);
|
|
942
1270
|
}
|
|
943
1271
|
const latencyMs = Date.now() - startTime;
|
|
1272
|
+
const sigv4Sensitive = /* @__PURE__ */ new Set(["authorization", "x-amz-date", "x-amz-content-sha256"]);
|
|
1273
|
+
const stripHeaders = isSigV4 ? sigv4Sensitive : /* @__PURE__ */ new Set([injectionHeaderLower]);
|
|
944
1274
|
const auditHeaders = {};
|
|
945
1275
|
for (const [key, value] of Object.entries(requestHeaders)) {
|
|
946
|
-
if (key.toLowerCase()
|
|
1276
|
+
if (!stripHeaders.has(key.toLowerCase())) {
|
|
947
1277
|
auditHeaders[key] = value;
|
|
948
1278
|
}
|
|
949
1279
|
}
|
|
@@ -954,19 +1284,19 @@ var AlterVault = class _AlterVault {
|
|
|
954
1284
|
});
|
|
955
1285
|
this.#scheduleAuditLog({
|
|
956
1286
|
connectionId: tokenResponse.connectionId,
|
|
957
|
-
providerId:
|
|
1287
|
+
providerId: tokenResponse.providerId || connectionId,
|
|
958
1288
|
method: methodStr,
|
|
959
1289
|
url,
|
|
960
1290
|
requestHeaders: auditHeaders,
|
|
961
|
-
requestBody: options
|
|
1291
|
+
requestBody: options?.json ?? null,
|
|
962
1292
|
responseStatus: response.status,
|
|
963
1293
|
responseHeaders,
|
|
964
1294
|
responseBody,
|
|
965
1295
|
latencyMs,
|
|
966
|
-
reason: options
|
|
967
|
-
runId
|
|
968
|
-
threadId: options
|
|
969
|
-
toolCallId: options
|
|
1296
|
+
reason: options?.reason ?? null,
|
|
1297
|
+
runId,
|
|
1298
|
+
threadId: options?.threadId ?? null,
|
|
1299
|
+
toolCallId: options?.toolCallId ?? null
|
|
970
1300
|
});
|
|
971
1301
|
if (response.status >= HTTP_CLIENT_ERROR_START) {
|
|
972
1302
|
throw new ProviderAPIError(
|
|
@@ -974,7 +1304,7 @@ var AlterVault = class _AlterVault {
|
|
|
974
1304
|
response.status,
|
|
975
1305
|
responseBody,
|
|
976
1306
|
{
|
|
977
|
-
|
|
1307
|
+
connection_id: connectionId,
|
|
978
1308
|
method: methodStr,
|
|
979
1309
|
url
|
|
980
1310
|
}
|
|
@@ -996,14 +1326,17 @@ var AlterVault = class _AlterVault {
|
|
|
996
1326
|
}
|
|
997
1327
|
const actorHeaders = this.#getActorRequestHeaders();
|
|
998
1328
|
let response;
|
|
1329
|
+
const listBody = {
|
|
1330
|
+
provider_id: options?.providerId ?? null,
|
|
1331
|
+
limit: options?.limit ?? 100,
|
|
1332
|
+
offset: options?.offset ?? 0
|
|
1333
|
+
};
|
|
1334
|
+
const listPath = "/sdk/oauth/connections/list";
|
|
1335
|
+
const listHmac = this.#computeHmacHeaders("POST", listPath, JSON.stringify(listBody));
|
|
999
1336
|
try {
|
|
1000
|
-
response = await this.#alterClient.post(
|
|
1001
|
-
json:
|
|
1002
|
-
|
|
1003
|
-
limit: options?.limit ?? 100,
|
|
1004
|
-
offset: options?.offset ?? 0
|
|
1005
|
-
},
|
|
1006
|
-
headers: actorHeaders
|
|
1337
|
+
response = await this.#alterClient.post(listPath, {
|
|
1338
|
+
json: listBody,
|
|
1339
|
+
headers: { ...actorHeaders, ...listHmac }
|
|
1007
1340
|
});
|
|
1008
1341
|
} catch (error) {
|
|
1009
1342
|
if (_AlterVault.#isTimeoutOrAbortError(error)) {
|
|
@@ -1054,17 +1387,19 @@ var AlterVault = class _AlterVault {
|
|
|
1054
1387
|
}
|
|
1055
1388
|
const actorHeaders = this.#getActorRequestHeaders();
|
|
1056
1389
|
let response;
|
|
1390
|
+
const sessionBody = {
|
|
1391
|
+
end_user: options.endUser,
|
|
1392
|
+
allowed_providers: options.allowedProviders ?? null,
|
|
1393
|
+
return_url: options.returnUrl ?? null,
|
|
1394
|
+
allowed_origin: options.allowedOrigin ?? null,
|
|
1395
|
+
metadata: options.metadata ?? null
|
|
1396
|
+
};
|
|
1397
|
+
const sessionPath = "/sdk/oauth/connect/session";
|
|
1398
|
+
const sessionHmac = this.#computeHmacHeaders("POST", sessionPath, JSON.stringify(sessionBody));
|
|
1057
1399
|
try {
|
|
1058
|
-
response = await this.#alterClient.post(
|
|
1059
|
-
json:
|
|
1060
|
-
|
|
1061
|
-
attributes: options.attributes ?? null,
|
|
1062
|
-
allowed_providers: options.allowedProviders ?? null,
|
|
1063
|
-
return_url: options.returnUrl ?? null,
|
|
1064
|
-
allowed_origin: options.allowedOrigin ?? null,
|
|
1065
|
-
metadata: options.metadata ?? null
|
|
1066
|
-
},
|
|
1067
|
-
headers: actorHeaders
|
|
1400
|
+
response = await this.#alterClient.post(sessionPath, {
|
|
1401
|
+
json: sessionBody,
|
|
1402
|
+
headers: { ...actorHeaders, ...sessionHmac }
|
|
1068
1403
|
});
|
|
1069
1404
|
} catch (error) {
|
|
1070
1405
|
if (_AlterVault.#isTimeoutOrAbortError(error)) {
|
|
@@ -1088,6 +1423,132 @@ var AlterVault = class _AlterVault {
|
|
|
1088
1423
|
const data = await response.json();
|
|
1089
1424
|
return new ConnectSession(data);
|
|
1090
1425
|
}
|
|
1426
|
+
/**
|
|
1427
|
+
* Open OAuth in the user's browser and wait for completion.
|
|
1428
|
+
*
|
|
1429
|
+
* This is the headless connect flow for CLI tools, scripts, and
|
|
1430
|
+
* server-side applications. It creates a Connect session, opens the
|
|
1431
|
+
* browser, and polls until the user completes OAuth.
|
|
1432
|
+
*
|
|
1433
|
+
* @param options - Connect options (endUser is required)
|
|
1434
|
+
* @returns Array of ConnectResult objects (one per connected provider)
|
|
1435
|
+
* @throws ConnectTimeoutError if the user doesn't complete within timeout
|
|
1436
|
+
* @throws ConnectFlowError if the user denies or provider returns error
|
|
1437
|
+
* @throws AlterSDKError if SDK is closed or session creation fails
|
|
1438
|
+
*/
|
|
1439
|
+
async connect(options) {
|
|
1440
|
+
if (this.#closed) {
|
|
1441
|
+
throw new AlterSDKError(
|
|
1442
|
+
"SDK instance has been closed. Create a new AlterVault instance to make requests."
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
const timeout = options.timeout ?? 300;
|
|
1446
|
+
const pollInterval = options.pollInterval ?? 2;
|
|
1447
|
+
const openBrowser = options.openBrowser ?? true;
|
|
1448
|
+
const session = await this.createConnectSession({
|
|
1449
|
+
endUser: options.endUser,
|
|
1450
|
+
allowedProviders: options.providers
|
|
1451
|
+
});
|
|
1452
|
+
if (openBrowser) {
|
|
1453
|
+
try {
|
|
1454
|
+
const openModule = await import("open");
|
|
1455
|
+
const openFn = openModule.default;
|
|
1456
|
+
if (openFn) {
|
|
1457
|
+
await openFn(session.connectUrl);
|
|
1458
|
+
} else {
|
|
1459
|
+
console.log(
|
|
1460
|
+
`Open this URL to authorize: ${session.connectUrl}`
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1463
|
+
} catch {
|
|
1464
|
+
console.log(
|
|
1465
|
+
`Open this URL to authorize: ${session.connectUrl}`
|
|
1466
|
+
);
|
|
1467
|
+
}
|
|
1468
|
+
} else {
|
|
1469
|
+
console.log(
|
|
1470
|
+
`Open this URL to authorize: ${session.connectUrl}`
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
1473
|
+
const deadline = Date.now() + timeout * 1e3;
|
|
1474
|
+
while (Date.now() < deadline) {
|
|
1475
|
+
await new Promise(
|
|
1476
|
+
(resolve) => setTimeout(resolve, pollInterval * 1e3)
|
|
1477
|
+
);
|
|
1478
|
+
const pollResult = await this.#pollSession(session.sessionToken);
|
|
1479
|
+
const pollStatus = pollResult.status;
|
|
1480
|
+
if (pollStatus === "completed") {
|
|
1481
|
+
const connectionsData = pollResult.connections ?? [];
|
|
1482
|
+
return connectionsData.map(
|
|
1483
|
+
(conn) => new ConnectResult({
|
|
1484
|
+
connection_id: conn.connection_id ?? "",
|
|
1485
|
+
provider_id: conn.provider_id ?? "",
|
|
1486
|
+
account_identifier: conn.account_identifier ?? null,
|
|
1487
|
+
scopes: conn.scopes ?? []
|
|
1488
|
+
})
|
|
1489
|
+
);
|
|
1490
|
+
}
|
|
1491
|
+
if (pollStatus === "error") {
|
|
1492
|
+
const err = pollResult.error;
|
|
1493
|
+
throw new ConnectFlowError(
|
|
1494
|
+
err?.error_message ?? "OAuth flow failed",
|
|
1495
|
+
{
|
|
1496
|
+
error_code: err?.error_code ?? "unknown_error"
|
|
1497
|
+
}
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
if (pollStatus === "expired") {
|
|
1501
|
+
throw new ConnectFlowError(
|
|
1502
|
+
"Connect session expired before OAuth was completed"
|
|
1503
|
+
);
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
throw new ConnectTimeoutError(
|
|
1507
|
+
`OAuth flow did not complete within ${timeout} seconds. The user may not have finished authorizing in the browser.`,
|
|
1508
|
+
{ timeout }
|
|
1509
|
+
);
|
|
1510
|
+
}
|
|
1511
|
+
/**
|
|
1512
|
+
* Poll the Connect session for completion status (INTERNAL).
|
|
1513
|
+
*
|
|
1514
|
+
* Makes an HMAC-signed POST to the poll endpoint.
|
|
1515
|
+
*/
|
|
1516
|
+
async #pollSession(sessionToken) {
|
|
1517
|
+
const actorHeaders = this.#getActorRequestHeaders();
|
|
1518
|
+
const pollPath = "/sdk/oauth/connect/session/poll";
|
|
1519
|
+
const pollBody = { session_token: sessionToken };
|
|
1520
|
+
const pollHmac = this.#computeHmacHeaders(
|
|
1521
|
+
"POST",
|
|
1522
|
+
pollPath,
|
|
1523
|
+
JSON.stringify(pollBody)
|
|
1524
|
+
);
|
|
1525
|
+
let response;
|
|
1526
|
+
try {
|
|
1527
|
+
response = await this.#alterClient.post(pollPath, {
|
|
1528
|
+
json: pollBody,
|
|
1529
|
+
headers: { ...actorHeaders, ...pollHmac }
|
|
1530
|
+
});
|
|
1531
|
+
} catch (error) {
|
|
1532
|
+
if (_AlterVault.#isTimeoutOrAbortError(error)) {
|
|
1533
|
+
throw new TimeoutError(
|
|
1534
|
+
`Request to Alter Vault backend timed out: ${error instanceof Error ? error.message : String(error)}`,
|
|
1535
|
+
{ base_url: this.baseUrl }
|
|
1536
|
+
);
|
|
1537
|
+
}
|
|
1538
|
+
if (error instanceof TypeError) {
|
|
1539
|
+
throw new NetworkError(
|
|
1540
|
+
`Failed to connect to Alter Vault backend: ${error.message}`,
|
|
1541
|
+
{ base_url: this.baseUrl }
|
|
1542
|
+
);
|
|
1543
|
+
}
|
|
1544
|
+
throw new AlterSDKError(
|
|
1545
|
+
`Failed to poll connect session: ${error instanceof Error ? error.message : String(error)}`
|
|
1546
|
+
);
|
|
1547
|
+
}
|
|
1548
|
+
this.#cacheActorIdFromResponse(response);
|
|
1549
|
+
await this.#handleErrorResponse(response);
|
|
1550
|
+
return await response.json();
|
|
1551
|
+
}
|
|
1091
1552
|
/**
|
|
1092
1553
|
* Close HTTP clients and release resources.
|
|
1093
1554
|
* Waits for any pending audit tasks before closing.
|
|
@@ -1116,8 +1577,6 @@ var Provider = /* @__PURE__ */ ((Provider2) => {
|
|
|
1116
1577
|
Provider2["GOOGLE"] = "google";
|
|
1117
1578
|
Provider2["GITHUB"] = "github";
|
|
1118
1579
|
Provider2["SLACK"] = "slack";
|
|
1119
|
-
Provider2["MICROSOFT"] = "microsoft";
|
|
1120
|
-
Provider2["SALESFORCE"] = "salesforce";
|
|
1121
1580
|
Provider2["SENTRY"] = "sentry";
|
|
1122
1581
|
return Provider2;
|
|
1123
1582
|
})(Provider || {});
|
|
@@ -1134,9 +1593,13 @@ var HttpMethod = /* @__PURE__ */ ((HttpMethod2) => {
|
|
|
1134
1593
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1135
1594
|
0 && (module.exports = {
|
|
1136
1595
|
APICallAuditLog,
|
|
1596
|
+
ActorType,
|
|
1137
1597
|
AlterSDKError,
|
|
1138
1598
|
AlterVault,
|
|
1599
|
+
ConnectFlowError,
|
|
1600
|
+
ConnectResult,
|
|
1139
1601
|
ConnectSession,
|
|
1602
|
+
ConnectTimeoutError,
|
|
1140
1603
|
ConnectionInfo,
|
|
1141
1604
|
ConnectionListResult,
|
|
1142
1605
|
ConnectionNotFoundError,
|