@forklaunch/core 2.1.0 → 3.0.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/bin/gateway-mock/routes/email.mjs +233 -0
- package/bin/gateway-mock/routes/models.mjs +192 -0
- package/bin/gateway-mock/routes/payments.mjs +541 -0
- package/bin/gateway-mock/routes/sms.mjs +209 -0
- package/bin/gateway-mock/routes/voice.mjs +237 -0
- package/bin/gateway-mock/routes/whatsapp.mjs +227 -0
- package/bin/gateway-mock/server.mjs +321 -0
- package/bin/migrate-compliant-fields.mjs +312 -0
- package/bin/model-gateway-mock.mjs +13 -0
- package/lib/http/emailClient.d.ts +102 -0
- package/lib/http/emailClient.d.ts.map +1 -0
- package/lib/http/index.d.ts +7 -0
- package/lib/http/index.d.ts.map +1 -1
- package/lib/http/index.js +850 -4
- package/lib/http/index.js.map +1 -1
- package/lib/http/index.mjs +809 -1
- package/lib/http/index.mjs.map +1 -1
- package/lib/http/instanceGateway.d.ts +113 -0
- package/lib/http/instanceGateway.d.ts.map +1 -0
- package/lib/http/modelGatewayClient.d.ts +126 -0
- package/lib/http/modelGatewayClient.d.ts.map +1 -0
- package/lib/http/paymentsClient.d.ts +123 -0
- package/lib/http/paymentsClient.d.ts.map +1 -0
- package/lib/http/smsClient.d.ts +123 -0
- package/lib/http/smsClient.d.ts.map +1 -0
- package/lib/http/voiceClient.d.ts +105 -0
- package/lib/http/voiceClient.d.ts.map +1 -0
- package/lib/http/whatsappClient.d.ts +118 -0
- package/lib/http/whatsappClient.d.ts.map +1 -0
- package/lib/objectstore/interfaces/objectstore.interface.d.ts +45 -0
- package/lib/objectstore/interfaces/objectstore.interface.d.ts.map +1 -1
- package/lib/persistence/compliancePropertyBuilder.d.ts.map +1 -1
- package/lib/persistence/complianceTypes.d.ts +10 -1
- package/lib/persistence/complianceTypes.d.ts.map +1 -1
- package/lib/persistence/compliantField.d.ts +203 -0
- package/lib/persistence/compliantField.d.ts.map +1 -0
- package/lib/persistence/defineComplianceEntity.d.ts.map +1 -1
- package/lib/persistence/encryptedType.d.ts +8 -1
- package/lib/persistence/encryptedType.d.ts.map +1 -1
- package/lib/persistence/fieldEncryptor.d.ts +47 -6
- package/lib/persistence/fieldEncryptor.d.ts.map +1 -1
- package/lib/persistence/index.d.ts +3 -2
- package/lib/persistence/index.d.ts.map +1 -1
- package/lib/persistence/index.js +987 -259
- package/lib/persistence/index.js.map +1 -1
- package/lib/persistence/index.mjs +976 -257
- package/lib/persistence/index.mjs.map +1 -1
- package/lib/persistence/keyRotation.d.ts +5 -0
- package/lib/persistence/keyRotation.d.ts.map +1 -1
- package/lib/services/index.js.map +1 -1
- package/lib/services/index.mjs.map +1 -1
- package/package.json +29 -13
package/lib/http/index.js
CHANGED
|
@@ -38,15 +38,41 @@ __export(http_exports, {
|
|
|
38
38
|
ATTR_HTTP_ROUTE: () => import_semantic_conventions.ATTR_HTTP_ROUTE,
|
|
39
39
|
ATTR_SERVICE_NAME: () => import_semantic_conventions.ATTR_SERVICE_NAME,
|
|
40
40
|
AuditLogger: () => AuditLogger,
|
|
41
|
+
EMAIL_LIMITS: () => EMAIL_LIMITS,
|
|
42
|
+
EmailRequestError: () => EmailRequestError,
|
|
41
43
|
ForklaunchExpressLikeApplication: () => ForklaunchExpressLikeApplication,
|
|
42
44
|
ForklaunchExpressLikeRouter: () => ForklaunchExpressLikeRouter,
|
|
43
45
|
HTTPStatuses: () => HTTPStatuses,
|
|
46
|
+
INSTANCE_GATEWAY_MOUNT: () => INSTANCE_GATEWAY_MOUNT,
|
|
47
|
+
InstanceGatewayRequestError: () => InstanceGatewayRequestError,
|
|
48
|
+
ModelGatewayRequestError: () => ModelGatewayRequestError,
|
|
44
49
|
OPENAPI_DEFAULT_VERSION: () => OPENAPI_DEFAULT_VERSION,
|
|
45
50
|
OpenTelemetryCollector: () => OpenTelemetryCollector,
|
|
51
|
+
PLATFORM_EVENTS_PATH: () => PLATFORM_EVENTS_PATH,
|
|
52
|
+
PLATFORM_EVENT_KEY_ID: () => PLATFORM_EVENT_KEY_ID,
|
|
53
|
+
PaymentsRequestError: () => PaymentsRequestError,
|
|
46
54
|
PinoLogger: () => PinoLogger,
|
|
55
|
+
PlatformEventVerificationError: () => PlatformEventVerificationError,
|
|
47
56
|
RateLimiter: () => RateLimiter,
|
|
57
|
+
SMS_MAX_SEGMENTS: () => SMS_MAX_SEGMENTS,
|
|
58
|
+
STRIPE_GATEWAY_ROUTE: () => STRIPE_GATEWAY_ROUTE,
|
|
59
|
+
SmsClient: () => SmsClient,
|
|
60
|
+
SmsRequestError: () => SmsRequestError,
|
|
61
|
+
SmsValidationError: () => SmsValidationError,
|
|
62
|
+
VOICE_ATTRIBUTE_LIMITS: () => VOICE_ATTRIBUTE_LIMITS,
|
|
63
|
+
VoiceClient: () => VoiceClient,
|
|
64
|
+
VoiceRequestError: () => VoiceRequestError,
|
|
65
|
+
WhatsAppRequestError: () => WhatsAppRequestError,
|
|
48
66
|
createContext: () => createContext,
|
|
67
|
+
createEmailClient: () => createEmailClient,
|
|
49
68
|
createHmacToken: () => createHmacToken,
|
|
69
|
+
createInstanceGatewayTransport: () => createInstanceGatewayTransport,
|
|
70
|
+
createModelGatewayClient: () => createModelGatewayClient,
|
|
71
|
+
createPaymentsClient: () => createPaymentsClient,
|
|
72
|
+
createSmsClient: () => createSmsClient,
|
|
73
|
+
createStripeClient: () => createStripeClient,
|
|
74
|
+
createVoiceClient: () => createVoiceClient,
|
|
75
|
+
createWhatsAppClient: () => createWhatsAppClient,
|
|
50
76
|
delete_: () => delete_,
|
|
51
77
|
discriminateAuthMethod: () => discriminateAuthMethod,
|
|
52
78
|
discriminateBody: () => discriminateBody,
|
|
@@ -66,10 +92,14 @@ __export(http_exports, {
|
|
|
66
92
|
httpRequestsInFlightCounter: () => httpRequestsInFlightCounter,
|
|
67
93
|
httpRequestsTotalCounter: () => httpRequestsTotalCounter,
|
|
68
94
|
httpServerDurationHistogram: () => httpServerDurationHistogram,
|
|
95
|
+
instanceGatewaySettings: () => instanceGatewaySettings,
|
|
69
96
|
isClientError: () => isClientError,
|
|
97
|
+
isE164: () => isE164,
|
|
98
|
+
isEmailAddress: () => isEmailAddress,
|
|
70
99
|
isForklaunchRequest: () => isForklaunchRequest,
|
|
71
100
|
isForklaunchRouter: () => isForklaunchRouter,
|
|
72
101
|
isInformational: () => isInformational,
|
|
102
|
+
isManagedInstance: () => isManagedInstance,
|
|
73
103
|
isPortBound: () => isPortBound,
|
|
74
104
|
isRedirection: () => isRedirection,
|
|
75
105
|
isServerError: () => isServerError,
|
|
@@ -85,9 +115,16 @@ __export(http_exports, {
|
|
|
85
115
|
put: () => put,
|
|
86
116
|
recordMetric: () => recordMetric,
|
|
87
117
|
resetProcessHandlersForTesting: () => resetProcessHandlersForTesting,
|
|
118
|
+
signPlatformEvent: () => signPlatformEvent,
|
|
119
|
+
smsSegments: () => smsSegments,
|
|
120
|
+
stripeGatewayFetch: () => stripeGatewayFetch,
|
|
88
121
|
trace: () => trace3,
|
|
89
122
|
typedAuthHandler: () => typedAuthHandler,
|
|
90
|
-
typedHandler: () => typedHandler
|
|
123
|
+
typedHandler: () => typedHandler,
|
|
124
|
+
validateEmailSendRequest: () => validateEmailSendRequest,
|
|
125
|
+
validateSmsSend: () => validateSmsSend,
|
|
126
|
+
verifyPlatformEvent: () => verifyPlatformEvent,
|
|
127
|
+
voiceRequestProblem: () => voiceRequestProblem
|
|
91
128
|
});
|
|
92
129
|
module.exports = __toCommonJS(http_exports);
|
|
93
130
|
|
|
@@ -2474,6 +2511,778 @@ function generateHmacAuthHeaders({
|
|
|
2474
2511
|
};
|
|
2475
2512
|
}
|
|
2476
2513
|
|
|
2514
|
+
// src/http/instanceGateway.ts
|
|
2515
|
+
var import_crypto4 = require("crypto");
|
|
2516
|
+
var INSTANCE_GATEWAY_MOUNT = "/instance-gateway";
|
|
2517
|
+
var PLATFORM_EVENT_KEY_ID = "platform";
|
|
2518
|
+
var PLATFORM_EVENTS_PATH = "/platform-events";
|
|
2519
|
+
var MAX_SKEW_MS = 5 * 60 * 1e3;
|
|
2520
|
+
var InstanceGatewayRequestError = class extends Error {
|
|
2521
|
+
constructor(status, message, retryAfterSeconds) {
|
|
2522
|
+
super(message);
|
|
2523
|
+
this.status = status;
|
|
2524
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
2525
|
+
}
|
|
2526
|
+
status;
|
|
2527
|
+
retryAfterSeconds;
|
|
2528
|
+
name = "InstanceGatewayRequestError";
|
|
2529
|
+
};
|
|
2530
|
+
function instanceGatewaySettings(options2 = {}, feature = "The instance gateway") {
|
|
2531
|
+
const gatewayUrl = options2.gatewayUrl ?? process.env.PLATFORM_GATEWAY_URL;
|
|
2532
|
+
const instanceId = options2.instanceId ?? process.env.INSTANCE_ID;
|
|
2533
|
+
const hmacKey = options2.hmacKey ?? process.env.INSTANCE_HMAC_KEY;
|
|
2534
|
+
if (!gatewayUrl || !instanceId || !hmacKey) {
|
|
2535
|
+
throw new Error(
|
|
2536
|
+
`${feature} is only available to instances hosted in managed mode: PLATFORM_GATEWAY_URL, INSTANCE_ID and INSTANCE_HMAC_KEY must be set`
|
|
2537
|
+
);
|
|
2538
|
+
}
|
|
2539
|
+
return { gatewayUrl, instanceId, hmacKey };
|
|
2540
|
+
}
|
|
2541
|
+
function isManagedInstance(env = process.env) {
|
|
2542
|
+
return Boolean(
|
|
2543
|
+
env.PLATFORM_GATEWAY_URL && env.INSTANCE_ID && env.INSTANCE_HMAC_KEY
|
|
2544
|
+
);
|
|
2545
|
+
}
|
|
2546
|
+
function trimTrailingSlashes(value) {
|
|
2547
|
+
let end = value.length;
|
|
2548
|
+
while (end > 0 && value[end - 1] === "/") end -= 1;
|
|
2549
|
+
return value.slice(0, end);
|
|
2550
|
+
}
|
|
2551
|
+
function createInstanceGatewayTransport(options2 = {}, feature) {
|
|
2552
|
+
const { gatewayUrl, instanceId, hmacKey } = instanceGatewaySettings(
|
|
2553
|
+
options2,
|
|
2554
|
+
feature
|
|
2555
|
+
);
|
|
2556
|
+
const fetchImpl = options2.fetch ?? fetch;
|
|
2557
|
+
const base = trimTrailingSlashes(gatewayUrl);
|
|
2558
|
+
const request = async (method, route, body, requestOptions = {}) => {
|
|
2559
|
+
const { authorization } = generateHmacAuthHeaders({
|
|
2560
|
+
secretKey: hmacKey,
|
|
2561
|
+
method,
|
|
2562
|
+
path: route,
|
|
2563
|
+
body,
|
|
2564
|
+
keyId: instanceId
|
|
2565
|
+
});
|
|
2566
|
+
const response = await fetchImpl(`${base}${INSTANCE_GATEWAY_MOUNT}${route}`, {
|
|
2567
|
+
method,
|
|
2568
|
+
headers: {
|
|
2569
|
+
...requestOptions.headers,
|
|
2570
|
+
authorization,
|
|
2571
|
+
...body ? { "content-type": "application/json" } : {}
|
|
2572
|
+
},
|
|
2573
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
2574
|
+
signal: requestOptions.signal
|
|
2575
|
+
});
|
|
2576
|
+
if (!response.ok) {
|
|
2577
|
+
const message = (await response.text().catch(() => "")).trim();
|
|
2578
|
+
const retryAfter = Number(response.headers.get("retry-after"));
|
|
2579
|
+
throw new InstanceGatewayRequestError(
|
|
2580
|
+
response.status,
|
|
2581
|
+
message || `Instance gateway answered ${response.status}`,
|
|
2582
|
+
Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter : void 0
|
|
2583
|
+
);
|
|
2584
|
+
}
|
|
2585
|
+
return response;
|
|
2586
|
+
};
|
|
2587
|
+
return {
|
|
2588
|
+
instanceId,
|
|
2589
|
+
request,
|
|
2590
|
+
async json(method, route, body, requestOptions) {
|
|
2591
|
+
return await (await request(method, route, body, requestOptions)).json();
|
|
2592
|
+
}
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2595
|
+
var PlatformEventVerificationError = class extends Error {
|
|
2596
|
+
name = "PlatformEventVerificationError";
|
|
2597
|
+
};
|
|
2598
|
+
var seenEventNonces = /* @__PURE__ */ new Map();
|
|
2599
|
+
function verifyPlatformEvent(request, options2 = {}) {
|
|
2600
|
+
const hmacKey = options2.hmacKey ?? process.env.INSTANCE_HMAC_KEY;
|
|
2601
|
+
if (!hmacKey) {
|
|
2602
|
+
throw new PlatformEventVerificationError(
|
|
2603
|
+
"INSTANCE_HMAC_KEY is not set: this service is not a managed instance"
|
|
2604
|
+
);
|
|
2605
|
+
}
|
|
2606
|
+
const header = request.headers.authorization ?? request.headers.Authorization;
|
|
2607
|
+
const value = Array.isArray(header) ? header[0] : header;
|
|
2608
|
+
const match = /^HMAC keyId=(\S+) ts=(\S+) nonce=(\S+) signature=(\S+)$/.exec(
|
|
2609
|
+
value ?? ""
|
|
2610
|
+
);
|
|
2611
|
+
if (!match) {
|
|
2612
|
+
throw new PlatformEventVerificationError(
|
|
2613
|
+
"Missing or malformed HMAC authorization"
|
|
2614
|
+
);
|
|
2615
|
+
}
|
|
2616
|
+
const [, keyId, ts, nonce, signature] = match;
|
|
2617
|
+
if (keyId !== PLATFORM_EVENT_KEY_ID) {
|
|
2618
|
+
throw new PlatformEventVerificationError("Not signed by the platform");
|
|
2619
|
+
}
|
|
2620
|
+
const timestamp = new Date(ts);
|
|
2621
|
+
const now = (options2.now ?? Date.now)();
|
|
2622
|
+
if (Number.isNaN(timestamp.getTime()) || Math.abs(now - timestamp.getTime()) > MAX_SKEW_MS) {
|
|
2623
|
+
throw new PlatformEventVerificationError("Stale or invalid timestamp");
|
|
2624
|
+
}
|
|
2625
|
+
const expected = Buffer.from(
|
|
2626
|
+
createHmacToken({
|
|
2627
|
+
secretKey: hmacKey,
|
|
2628
|
+
method: request.method.toUpperCase(),
|
|
2629
|
+
path: request.path,
|
|
2630
|
+
body: request.body,
|
|
2631
|
+
timestamp,
|
|
2632
|
+
nonce
|
|
2633
|
+
})
|
|
2634
|
+
);
|
|
2635
|
+
const received = Buffer.from(signature);
|
|
2636
|
+
if (expected.length !== received.length || !(0, import_crypto4.timingSafeEqual)(expected, received)) {
|
|
2637
|
+
throw new PlatformEventVerificationError("Invalid signature");
|
|
2638
|
+
}
|
|
2639
|
+
for (const [seen, at] of seenEventNonces) {
|
|
2640
|
+
if (now - at > MAX_SKEW_MS) seenEventNonces.delete(seen);
|
|
2641
|
+
}
|
|
2642
|
+
if (seenEventNonces.has(nonce)) {
|
|
2643
|
+
throw new PlatformEventVerificationError("Replayed delivery");
|
|
2644
|
+
}
|
|
2645
|
+
seenEventNonces.set(nonce, now);
|
|
2646
|
+
const parsed = typeof request.body === "string" || Buffer.isBuffer(request.body) ? JSON.parse(request.body.toString()) : request.body;
|
|
2647
|
+
const event = parsed;
|
|
2648
|
+
if (!event || typeof event.id !== "string" || typeof event.type !== "string" || typeof event.feature !== "string") {
|
|
2649
|
+
throw new PlatformEventVerificationError("Body is not a platform event");
|
|
2650
|
+
}
|
|
2651
|
+
return event;
|
|
2652
|
+
}
|
|
2653
|
+
function signPlatformEvent(params) {
|
|
2654
|
+
return generateHmacAuthHeaders({
|
|
2655
|
+
secretKey: params.hmacKey,
|
|
2656
|
+
method: "POST",
|
|
2657
|
+
path: params.path,
|
|
2658
|
+
body: params.body,
|
|
2659
|
+
keyId: PLATFORM_EVENT_KEY_ID
|
|
2660
|
+
});
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
// src/http/modelGatewayClient.ts
|
|
2664
|
+
var ModelGatewayRequestError = class extends InstanceGatewayRequestError {
|
|
2665
|
+
name = "ModelGatewayRequestError";
|
|
2666
|
+
};
|
|
2667
|
+
function createModelGatewayClient(options2 = {}) {
|
|
2668
|
+
const transport = createInstanceGatewayTransport(options2, "The model gateway");
|
|
2669
|
+
async function send(method, route, body, signal) {
|
|
2670
|
+
try {
|
|
2671
|
+
return await transport.request(method, route, body, { signal });
|
|
2672
|
+
} catch (error) {
|
|
2673
|
+
if (error instanceof InstanceGatewayRequestError) {
|
|
2674
|
+
throw new ModelGatewayRequestError(
|
|
2675
|
+
error.status,
|
|
2676
|
+
error.message,
|
|
2677
|
+
error.retryAfterSeconds
|
|
2678
|
+
);
|
|
2679
|
+
}
|
|
2680
|
+
throw error;
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
return {
|
|
2684
|
+
async models() {
|
|
2685
|
+
return await (await send("GET", "/models")).json();
|
|
2686
|
+
},
|
|
2687
|
+
chat: {
|
|
2688
|
+
completions: {
|
|
2689
|
+
async create(request) {
|
|
2690
|
+
const response = await send(
|
|
2691
|
+
"POST",
|
|
2692
|
+
"/models/chat/completions",
|
|
2693
|
+
request
|
|
2694
|
+
);
|
|
2695
|
+
return await response.json();
|
|
2696
|
+
},
|
|
2697
|
+
async *stream(request, streamOptions = {}) {
|
|
2698
|
+
const response = await send(
|
|
2699
|
+
"POST",
|
|
2700
|
+
"/models/chat/completions/stream",
|
|
2701
|
+
request,
|
|
2702
|
+
streamOptions.signal
|
|
2703
|
+
);
|
|
2704
|
+
if (!response.body) return;
|
|
2705
|
+
for await (const data of sseData(response.body)) {
|
|
2706
|
+
if (data === "[DONE]") return;
|
|
2707
|
+
try {
|
|
2708
|
+
yield JSON.parse(data);
|
|
2709
|
+
} catch {
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
};
|
|
2716
|
+
}
|
|
2717
|
+
async function* sseData(stream) {
|
|
2718
|
+
const decoder = new TextDecoder();
|
|
2719
|
+
let buffer = "";
|
|
2720
|
+
const reader = stream.getReader();
|
|
2721
|
+
try {
|
|
2722
|
+
for (; ; ) {
|
|
2723
|
+
const { value, done } = await reader.read();
|
|
2724
|
+
if (done) break;
|
|
2725
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2726
|
+
let boundary;
|
|
2727
|
+
while ((boundary = buffer.search(/\r?\n\r?\n/)) !== -1) {
|
|
2728
|
+
const event = buffer.slice(0, boundary);
|
|
2729
|
+
buffer = buffer.slice(boundary).replace(/^\r?\n\r?\n/, "");
|
|
2730
|
+
const data = event.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
|
|
2731
|
+
if (data) yield data;
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
const tail = buffer.trim();
|
|
2735
|
+
if (tail.startsWith("data:")) yield tail.slice(5).trimStart();
|
|
2736
|
+
} finally {
|
|
2737
|
+
reader.releaseLock();
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
// src/http/emailClient.ts
|
|
2742
|
+
var EmailRequestError = class extends InstanceGatewayRequestError {
|
|
2743
|
+
name = "EmailRequestError";
|
|
2744
|
+
};
|
|
2745
|
+
var EMAIL_LIMITS = {
|
|
2746
|
+
maxRecipients: 50,
|
|
2747
|
+
maxAddressLength: 254,
|
|
2748
|
+
maxSubjectLength: 998,
|
|
2749
|
+
/** text + html, UTF-8 bytes. */
|
|
2750
|
+
maxBodyBytes: 512 * 1024,
|
|
2751
|
+
maxTags: 10
|
|
2752
|
+
};
|
|
2753
|
+
var ADDRESS = /^[^\s@<>()[\]\\,;:"]+@[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)+$/;
|
|
2754
|
+
var TAG_PART = /^[A-Za-z0-9_-]{1,256}$/;
|
|
2755
|
+
function isEmailAddress(address) {
|
|
2756
|
+
return typeof address === "string" && address.length <= EMAIL_LIMITS.maxAddressLength && ADDRESS.test(address);
|
|
2757
|
+
}
|
|
2758
|
+
function refuse(message) {
|
|
2759
|
+
throw new EmailRequestError(400, message);
|
|
2760
|
+
}
|
|
2761
|
+
function addresses(value, field) {
|
|
2762
|
+
if (value === void 0) return void 0;
|
|
2763
|
+
const list = Array.isArray(value) ? value : [value];
|
|
2764
|
+
if (list.length === 0) refuse(`${field} must name at least one address`);
|
|
2765
|
+
if (list.length > EMAIL_LIMITS.maxRecipients) {
|
|
2766
|
+
refuse(
|
|
2767
|
+
`${field} has ${list.length} addresses; the limit is ${EMAIL_LIMITS.maxRecipients}`
|
|
2768
|
+
);
|
|
2769
|
+
}
|
|
2770
|
+
for (const address of list) {
|
|
2771
|
+
if (!isEmailAddress(address)) {
|
|
2772
|
+
refuse(
|
|
2773
|
+
`${field}: '${String(address).slice(0, 80)}' is not an email address`
|
|
2774
|
+
);
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
return list;
|
|
2778
|
+
}
|
|
2779
|
+
function validateEmailSendRequest(request) {
|
|
2780
|
+
const to = addresses(request.to, "to") ?? refuse("to is required");
|
|
2781
|
+
const replyTo = addresses(request.replyTo, "replyTo");
|
|
2782
|
+
const subject = request.subject;
|
|
2783
|
+
if (typeof subject !== "string" || subject.trim() === "") {
|
|
2784
|
+
refuse("subject is required");
|
|
2785
|
+
}
|
|
2786
|
+
if (/[\r\n]/.test(subject)) refuse("subject must be one line");
|
|
2787
|
+
if (subject.length > EMAIL_LIMITS.maxSubjectLength) {
|
|
2788
|
+
refuse(
|
|
2789
|
+
`subject is longer than ${EMAIL_LIMITS.maxSubjectLength} characters`
|
|
2790
|
+
);
|
|
2791
|
+
}
|
|
2792
|
+
if (!request.text && !request.html) refuse("text or html is required");
|
|
2793
|
+
const bytes = Buffer.byteLength(request.text ?? "", "utf8") + Buffer.byteLength(request.html ?? "", "utf8");
|
|
2794
|
+
if (bytes > EMAIL_LIMITS.maxBodyBytes) {
|
|
2795
|
+
refuse(`body is ${bytes} bytes; the limit is ${EMAIL_LIMITS.maxBodyBytes}`);
|
|
2796
|
+
}
|
|
2797
|
+
const tags = request.tags ?? {};
|
|
2798
|
+
const names = Object.keys(tags);
|
|
2799
|
+
if (names.length > EMAIL_LIMITS.maxTags) {
|
|
2800
|
+
refuse(`${names.length} tags; the limit is ${EMAIL_LIMITS.maxTags}`);
|
|
2801
|
+
}
|
|
2802
|
+
for (const name of names) {
|
|
2803
|
+
if (!TAG_PART.test(name) || !TAG_PART.test(String(tags[name]))) {
|
|
2804
|
+
refuse(`tag '${name}': names and values are letters, digits, _ and -`);
|
|
2805
|
+
}
|
|
2806
|
+
if (name.startsWith("fl-") || name.startsWith("ses:")) {
|
|
2807
|
+
refuse(`tag '${name}' is reserved`);
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
return {
|
|
2811
|
+
to,
|
|
2812
|
+
subject,
|
|
2813
|
+
...request.text !== void 0 ? { text: request.text } : {},
|
|
2814
|
+
...request.html !== void 0 ? { html: request.html } : {},
|
|
2815
|
+
...replyTo ? { replyTo } : {},
|
|
2816
|
+
...names.length ? { tags } : {}
|
|
2817
|
+
};
|
|
2818
|
+
}
|
|
2819
|
+
function createEmailClient(options2 = {}) {
|
|
2820
|
+
if (!isManagedInstance({
|
|
2821
|
+
PLATFORM_GATEWAY_URL: options2.gatewayUrl ?? process.env.PLATFORM_GATEWAY_URL,
|
|
2822
|
+
INSTANCE_ID: options2.instanceId ?? process.env.INSTANCE_ID,
|
|
2823
|
+
INSTANCE_HMAC_KEY: options2.hmacKey ?? process.env.INSTANCE_HMAC_KEY
|
|
2824
|
+
})) {
|
|
2825
|
+
throw new Error(
|
|
2826
|
+
"Email is sent by the ForkLaunch platform and is only available to managed instances: PLATFORM_GATEWAY_URL, INSTANCE_ID and INSTANCE_HMAC_KEY must be set. Locally, `forklaunch infra add <service> email` sets them to the gateway mock in docker-compose."
|
|
2827
|
+
);
|
|
2828
|
+
}
|
|
2829
|
+
const transport = createInstanceGatewayTransport(options2, "Email");
|
|
2830
|
+
async function call(method, route, body) {
|
|
2831
|
+
try {
|
|
2832
|
+
return await transport.json(method, route, body);
|
|
2833
|
+
} catch (error) {
|
|
2834
|
+
if (error instanceof InstanceGatewayRequestError) {
|
|
2835
|
+
throw new EmailRequestError(
|
|
2836
|
+
error.status,
|
|
2837
|
+
error.message,
|
|
2838
|
+
error.retryAfterSeconds
|
|
2839
|
+
);
|
|
2840
|
+
}
|
|
2841
|
+
throw error;
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
return {
|
|
2845
|
+
async send(request) {
|
|
2846
|
+
return call(
|
|
2847
|
+
"POST",
|
|
2848
|
+
"/email/send",
|
|
2849
|
+
validateEmailSendRequest(request)
|
|
2850
|
+
);
|
|
2851
|
+
},
|
|
2852
|
+
async suppressed(address) {
|
|
2853
|
+
if (!isEmailAddress(address)) {
|
|
2854
|
+
refuse(`'${String(address).slice(0, 80)}' is not an email address`);
|
|
2855
|
+
}
|
|
2856
|
+
const answer = await call(
|
|
2857
|
+
"GET",
|
|
2858
|
+
`/email/suppressions/${encodeURIComponent(address.toLowerCase())}`
|
|
2859
|
+
);
|
|
2860
|
+
return answer.suppressed === true;
|
|
2861
|
+
}
|
|
2862
|
+
};
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
// src/http/smsClient.ts
|
|
2866
|
+
var SmsRequestError = class extends InstanceGatewayRequestError {
|
|
2867
|
+
name = "SmsRequestError";
|
|
2868
|
+
};
|
|
2869
|
+
var SmsValidationError = class extends Error {
|
|
2870
|
+
name = "SmsValidationError";
|
|
2871
|
+
};
|
|
2872
|
+
var SMS_MAX_SEGMENTS = 10;
|
|
2873
|
+
var E164 = /^\+[1-9]\d{6,14}$/;
|
|
2874
|
+
function isE164(phone) {
|
|
2875
|
+
return E164.test(phone);
|
|
2876
|
+
}
|
|
2877
|
+
var GSM_BASIC = new Set(
|
|
2878
|
+
`@\xA3$\xA5\xE8\xE9\xF9\xEC\xF2\xC7
|
|
2879
|
+
\xD8\xF8\r\xC5\xE5\u0394_\u03A6\u0393\u039B\u03A9\u03A0\u03A8\u03A3\u0398\u039E\xC6\xE6\xDF\xC9 !"#\xA4%&'()*+,-./0123456789:;<=>?\xA1ABCDEFGHIJKLMNOPQRSTUVWXYZ\xC4\xD6\xD1\xDC\xA7\xBFabcdefghijklmnopqrstuvwxyz\xE4\xF6\xF1\xFC\xE0`
|
|
2880
|
+
);
|
|
2881
|
+
var GSM_EXTENDED = new Set("^{}\\[~]|\u20AC\f");
|
|
2882
|
+
function smsSegments(body) {
|
|
2883
|
+
let septets = 0;
|
|
2884
|
+
let gsm = true;
|
|
2885
|
+
for (const ch of body) {
|
|
2886
|
+
if (GSM_BASIC.has(ch)) septets += 1;
|
|
2887
|
+
else if (GSM_EXTENDED.has(ch)) septets += 2;
|
|
2888
|
+
else {
|
|
2889
|
+
gsm = false;
|
|
2890
|
+
break;
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
if (gsm) {
|
|
2894
|
+
return {
|
|
2895
|
+
encoding: "GSM-7",
|
|
2896
|
+
units: septets,
|
|
2897
|
+
segments: septets <= 160 ? 1 : Math.ceil(septets / 153)
|
|
2898
|
+
};
|
|
2899
|
+
}
|
|
2900
|
+
const units = body.length;
|
|
2901
|
+
return {
|
|
2902
|
+
encoding: "UCS-2",
|
|
2903
|
+
units,
|
|
2904
|
+
segments: units <= 70 ? 1 : Math.ceil(units / 67)
|
|
2905
|
+
};
|
|
2906
|
+
}
|
|
2907
|
+
function validateSmsSend(request) {
|
|
2908
|
+
if (typeof request.to !== "string" || !isE164(request.to)) {
|
|
2909
|
+
throw new SmsValidationError(
|
|
2910
|
+
`'to' must be an E.164 number like +14155550123 (got ${JSON.stringify(request.to)})`
|
|
2911
|
+
);
|
|
2912
|
+
}
|
|
2913
|
+
if (typeof request.body !== "string" || !request.body.trim()) {
|
|
2914
|
+
throw new SmsValidationError("'body' must be non-empty text");
|
|
2915
|
+
}
|
|
2916
|
+
const { segments, encoding } = smsSegments(request.body);
|
|
2917
|
+
if (segments > SMS_MAX_SEGMENTS) {
|
|
2918
|
+
throw new SmsValidationError(
|
|
2919
|
+
`'body' is ${segments} ${encoding} segments; the limit is ${SMS_MAX_SEGMENTS}. Send a link instead of long text.`
|
|
2920
|
+
);
|
|
2921
|
+
}
|
|
2922
|
+
if (request.purpose !== void 0 && request.purpose !== "transactional" && request.purpose !== "promotional") {
|
|
2923
|
+
throw new SmsValidationError(
|
|
2924
|
+
"'purpose' must be 'transactional' or 'promotional'"
|
|
2925
|
+
);
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
var NOT_MANAGED = "SMS is platform-held: this service must run as a managed instance (PLATFORM_GATEWAY_URL, INSTANCE_ID and INSTANCE_HMAC_KEY set). Locally, run the gateway mock (npx -p @forklaunch/core forklaunch-gateway-mock) and point those variables at it; do not add a Twilio or AWS SDK.";
|
|
2929
|
+
var SmsClient = class {
|
|
2930
|
+
transport;
|
|
2931
|
+
constructor(options2 = {}) {
|
|
2932
|
+
const configured = Boolean(
|
|
2933
|
+
(options2.gatewayUrl ?? process.env.PLATFORM_GATEWAY_URL) && (options2.instanceId ?? process.env.INSTANCE_ID) && (options2.hmacKey ?? process.env.INSTANCE_HMAC_KEY)
|
|
2934
|
+
) || isManagedInstance();
|
|
2935
|
+
if (configured) {
|
|
2936
|
+
this.transport = createInstanceGatewayTransport(options2, "SMS");
|
|
2937
|
+
} else if (!options2.deferRefusal) {
|
|
2938
|
+
throw new Error(NOT_MANAGED);
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
/** Whether sends can reach the gateway (false: every send refuses). */
|
|
2942
|
+
get managed() {
|
|
2943
|
+
return this.transport !== void 0;
|
|
2944
|
+
}
|
|
2945
|
+
async send(request) {
|
|
2946
|
+
validateSmsSend(request);
|
|
2947
|
+
if (!this.transport) throw new Error(NOT_MANAGED);
|
|
2948
|
+
try {
|
|
2949
|
+
return await this.transport.json("POST", "/sms/send", {
|
|
2950
|
+
to: request.to,
|
|
2951
|
+
body: request.body,
|
|
2952
|
+
purpose: request.purpose ?? "transactional"
|
|
2953
|
+
});
|
|
2954
|
+
} catch (error) {
|
|
2955
|
+
if (error instanceof InstanceGatewayRequestError) {
|
|
2956
|
+
throw new SmsRequestError(
|
|
2957
|
+
error.status,
|
|
2958
|
+
error.message,
|
|
2959
|
+
error.retryAfterSeconds
|
|
2960
|
+
);
|
|
2961
|
+
}
|
|
2962
|
+
throw error;
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
};
|
|
2966
|
+
function createSmsClient(options2 = {}) {
|
|
2967
|
+
return new SmsClient(options2);
|
|
2968
|
+
}
|
|
2969
|
+
|
|
2970
|
+
// src/http/whatsappClient.ts
|
|
2971
|
+
var WhatsAppRequestError = class extends InstanceGatewayRequestError {
|
|
2972
|
+
name = "WhatsAppRequestError";
|
|
2973
|
+
};
|
|
2974
|
+
var E1642 = /^\+[1-9]\d{6,14}$/;
|
|
2975
|
+
function requireRecipient(to) {
|
|
2976
|
+
if (typeof to !== "string" || !E1642.test(to)) {
|
|
2977
|
+
throw new WhatsAppRequestError(
|
|
2978
|
+
400,
|
|
2979
|
+
`'to' must be an E.164 phone number such as +14155550123 (got ${JSON.stringify(to)})`
|
|
2980
|
+
);
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
function createConversationChannelClient(channel, label, options2) {
|
|
2984
|
+
const transport = createInstanceGatewayTransport(options2, label);
|
|
2985
|
+
async function call(method, route, body) {
|
|
2986
|
+
try {
|
|
2987
|
+
return await transport.json(method, `/${channel}${route}`, body);
|
|
2988
|
+
} catch (error) {
|
|
2989
|
+
if (error instanceof InstanceGatewayRequestError) {
|
|
2990
|
+
throw new WhatsAppRequestError(
|
|
2991
|
+
error.status,
|
|
2992
|
+
error.message,
|
|
2993
|
+
error.retryAfterSeconds
|
|
2994
|
+
);
|
|
2995
|
+
}
|
|
2996
|
+
throw error;
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
return {
|
|
3000
|
+
async sendTemplate(request) {
|
|
3001
|
+
requireRecipient(request.to);
|
|
3002
|
+
if (!request.template || !request.language) {
|
|
3003
|
+
throw new WhatsAppRequestError(
|
|
3004
|
+
400,
|
|
3005
|
+
"template and language are required"
|
|
3006
|
+
);
|
|
3007
|
+
}
|
|
3008
|
+
return call("POST", "/messages", {
|
|
3009
|
+
to: request.to,
|
|
3010
|
+
type: "template",
|
|
3011
|
+
template: request.template,
|
|
3012
|
+
language: request.language,
|
|
3013
|
+
...request.components ? { components: request.components } : {}
|
|
3014
|
+
});
|
|
3015
|
+
},
|
|
3016
|
+
async sendText(request) {
|
|
3017
|
+
requireRecipient(request.to);
|
|
3018
|
+
if (typeof request.body !== "string" || !request.body.trim()) {
|
|
3019
|
+
throw new WhatsAppRequestError(400, "body must be non-empty text");
|
|
3020
|
+
}
|
|
3021
|
+
return call("POST", "/messages", {
|
|
3022
|
+
to: request.to,
|
|
3023
|
+
type: "text",
|
|
3024
|
+
body: request.body
|
|
3025
|
+
});
|
|
3026
|
+
},
|
|
3027
|
+
async templates() {
|
|
3028
|
+
const listing = await call(
|
|
3029
|
+
"GET",
|
|
3030
|
+
"/templates"
|
|
3031
|
+
);
|
|
3032
|
+
return listing.templates;
|
|
3033
|
+
}
|
|
3034
|
+
};
|
|
3035
|
+
}
|
|
3036
|
+
function createWhatsAppClient(options2 = {}) {
|
|
3037
|
+
return createConversationChannelClient("whatsapp", "WhatsApp", options2);
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
// src/http/voiceClient.ts
|
|
3041
|
+
var VOICE_ATTRIBUTE_LIMITS = {
|
|
3042
|
+
maxKeys: 20,
|
|
3043
|
+
maxKeyLength: 64,
|
|
3044
|
+
maxValueLength: 256,
|
|
3045
|
+
maxTotalBytes: 4096,
|
|
3046
|
+
/** Keys the platform reserves (it prefixes and adds its own under `fl_`). */
|
|
3047
|
+
reservedPrefix: "fl_"
|
|
3048
|
+
};
|
|
3049
|
+
var VoiceRequestError = class extends InstanceGatewayRequestError {
|
|
3050
|
+
name = "VoiceRequestError";
|
|
3051
|
+
};
|
|
3052
|
+
var E1643 = /^\+[1-9]\d{6,14}$/;
|
|
3053
|
+
var FLOW_NAME = /^[a-z][a-z0-9_-]{0,63}$/;
|
|
3054
|
+
var ATTRIBUTE_KEY = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
3055
|
+
function voiceRequestProblem(request) {
|
|
3056
|
+
if (typeof request.to !== "string" || !E1643.test(request.to)) {
|
|
3057
|
+
return "`to` must be an E.164 number such as +15551230000";
|
|
3058
|
+
}
|
|
3059
|
+
if (typeof request.flow !== "string" || request.flow.startsWith("arn:")) {
|
|
3060
|
+
return "`flow` is a flow name from the product catalog (e.g. appointment_reminder), not a contact-flow ARN";
|
|
3061
|
+
}
|
|
3062
|
+
if (!FLOW_NAME.test(request.flow)) {
|
|
3063
|
+
return "`flow` must be a catalog flow name: lowercase letters, digits, _ or -";
|
|
3064
|
+
}
|
|
3065
|
+
const attributes = request.attributes ?? {};
|
|
3066
|
+
if (typeof attributes !== "object" || Array.isArray(attributes)) {
|
|
3067
|
+
return "`attributes` must be an object of strings";
|
|
3068
|
+
}
|
|
3069
|
+
const entries = Object.entries(attributes);
|
|
3070
|
+
const limits = VOICE_ATTRIBUTE_LIMITS;
|
|
3071
|
+
if (entries.length > limits.maxKeys) {
|
|
3072
|
+
return `at most ${limits.maxKeys} attributes`;
|
|
3073
|
+
}
|
|
3074
|
+
let total = 0;
|
|
3075
|
+
for (const [key, value] of entries) {
|
|
3076
|
+
if (!ATTRIBUTE_KEY.test(key) || key.length > limits.maxKeyLength) {
|
|
3077
|
+
return `attribute key '${key}' must be letters, digits or _ (max ${limits.maxKeyLength})`;
|
|
3078
|
+
}
|
|
3079
|
+
if (key.toLowerCase().startsWith(limits.reservedPrefix)) {
|
|
3080
|
+
return `attribute keys starting with '${limits.reservedPrefix}' are reserved for the platform`;
|
|
3081
|
+
}
|
|
3082
|
+
if (typeof value !== "string") {
|
|
3083
|
+
return `attribute '${key}' must be a string`;
|
|
3084
|
+
}
|
|
3085
|
+
if (value.length > limits.maxValueLength) {
|
|
3086
|
+
return `attribute '${key}' is longer than ${limits.maxValueLength} characters`;
|
|
3087
|
+
}
|
|
3088
|
+
total += Buffer.byteLength(key) + Buffer.byteLength(value);
|
|
3089
|
+
}
|
|
3090
|
+
if (total > limits.maxTotalBytes) {
|
|
3091
|
+
return `attributes exceed ${limits.maxTotalBytes} bytes`;
|
|
3092
|
+
}
|
|
3093
|
+
return void 0;
|
|
3094
|
+
}
|
|
3095
|
+
var VoiceClient = class {
|
|
3096
|
+
transport;
|
|
3097
|
+
/** Throws when the process is not a managed instance (see instanceGatewaySettings). */
|
|
3098
|
+
constructor(options2 = {}) {
|
|
3099
|
+
this.transport = createInstanceGatewayTransport(
|
|
3100
|
+
options2,
|
|
3101
|
+
"Voice calls (createVoiceClient)"
|
|
3102
|
+
);
|
|
3103
|
+
}
|
|
3104
|
+
/** Place an outbound call; resolves once the platform accepted it. */
|
|
3105
|
+
async startOutboundCall(request) {
|
|
3106
|
+
const problem = voiceRequestProblem(request);
|
|
3107
|
+
if (problem) throw new VoiceRequestError(400, problem);
|
|
3108
|
+
return this.send("POST", "/voice/calls", {
|
|
3109
|
+
to: request.to,
|
|
3110
|
+
flow: request.flow,
|
|
3111
|
+
...request.attributes ? { attributes: request.attributes } : {}
|
|
3112
|
+
});
|
|
3113
|
+
}
|
|
3114
|
+
/** Hang up a call this instance placed. */
|
|
3115
|
+
async endCall(callId) {
|
|
3116
|
+
await this.send("DELETE", callPath(callId));
|
|
3117
|
+
}
|
|
3118
|
+
/** Where a call this instance placed stands. */
|
|
3119
|
+
async call(callId) {
|
|
3120
|
+
return this.send("GET", callPath(callId));
|
|
3121
|
+
}
|
|
3122
|
+
async send(method, route, body) {
|
|
3123
|
+
try {
|
|
3124
|
+
const response = await this.transport.request(method, route, body);
|
|
3125
|
+
const text = await response.text();
|
|
3126
|
+
return text ? JSON.parse(text) : void 0;
|
|
3127
|
+
} catch (error) {
|
|
3128
|
+
if (error instanceof InstanceGatewayRequestError) {
|
|
3129
|
+
throw new VoiceRequestError(
|
|
3130
|
+
error.status,
|
|
3131
|
+
error.message,
|
|
3132
|
+
error.retryAfterSeconds
|
|
3133
|
+
);
|
|
3134
|
+
}
|
|
3135
|
+
throw error;
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
};
|
|
3139
|
+
function callPath(callId) {
|
|
3140
|
+
if (typeof callId !== "string" || !/^[A-Za-z0-9_-]{1,128}$/.test(callId)) {
|
|
3141
|
+
throw new VoiceRequestError(400, "callId is not a call id");
|
|
3142
|
+
}
|
|
3143
|
+
return `/voice/calls/${callId}`;
|
|
3144
|
+
}
|
|
3145
|
+
function createVoiceClient(options2 = {}) {
|
|
3146
|
+
return new VoiceClient(options2);
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
// src/http/paymentsClient.ts
|
|
3150
|
+
var import_node_module = require("module");
|
|
3151
|
+
var import_meta = {};
|
|
3152
|
+
var STRIPE_GATEWAY_ROUTE = "/stripe";
|
|
3153
|
+
var PaymentsRequestError = class extends InstanceGatewayRequestError {
|
|
3154
|
+
name = "PaymentsRequestError";
|
|
3155
|
+
};
|
|
3156
|
+
function createPaymentsClient(options2 = {}) {
|
|
3157
|
+
const transport = createInstanceGatewayTransport(options2, "Payments");
|
|
3158
|
+
async function call(method, route, body) {
|
|
3159
|
+
try {
|
|
3160
|
+
return await transport.json(method, route, body);
|
|
3161
|
+
} catch (error) {
|
|
3162
|
+
if (error instanceof InstanceGatewayRequestError) {
|
|
3163
|
+
throw new PaymentsRequestError(
|
|
3164
|
+
error.status,
|
|
3165
|
+
error.message,
|
|
3166
|
+
error.retryAfterSeconds
|
|
3167
|
+
);
|
|
3168
|
+
}
|
|
3169
|
+
throw error;
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
return {
|
|
3173
|
+
onboardingLink({ returnUrl, refreshUrl }) {
|
|
3174
|
+
for (const [name, value] of [
|
|
3175
|
+
["returnUrl", returnUrl],
|
|
3176
|
+
["refreshUrl", refreshUrl]
|
|
3177
|
+
]) {
|
|
3178
|
+
if (!/^https?:\/\//.test(value ?? "")) {
|
|
3179
|
+
throw new TypeError(`${name} must be an absolute http(s) URL`);
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
return call("POST", "/payments/onboarding-link", {
|
|
3183
|
+
returnUrl,
|
|
3184
|
+
refreshUrl
|
|
3185
|
+
});
|
|
3186
|
+
},
|
|
3187
|
+
status() {
|
|
3188
|
+
return call("GET", "/payments/status");
|
|
3189
|
+
}
|
|
3190
|
+
};
|
|
3191
|
+
}
|
|
3192
|
+
function loadStripe() {
|
|
3193
|
+
const load = (0, import_node_module.createRequire)(
|
|
3194
|
+
typeof __filename === "string" ? __filename : import_meta.url
|
|
3195
|
+
);
|
|
3196
|
+
try {
|
|
3197
|
+
const mod = load("stripe");
|
|
3198
|
+
return mod.default ?? mod;
|
|
3199
|
+
} catch {
|
|
3200
|
+
throw new Error(
|
|
3201
|
+
"createStripeClient needs the 'stripe' package: add it to this service (pnpm add stripe)"
|
|
3202
|
+
);
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
function stripeGatewayFetch(settings, fetchImpl = fetch) {
|
|
3206
|
+
const base = new URL(settings.gatewayUrl);
|
|
3207
|
+
const basePath = base.pathname.replace(/\/+$/, "");
|
|
3208
|
+
return async (input, init = {}) => {
|
|
3209
|
+
const original = new URL(
|
|
3210
|
+
typeof input === "string" || input instanceof URL ? input : input.url
|
|
3211
|
+
);
|
|
3212
|
+
const routerPath = `${STRIPE_GATEWAY_ROUTE}${original.pathname}${original.search}`;
|
|
3213
|
+
const method = (init.method ?? "GET").toUpperCase();
|
|
3214
|
+
const body = typeof init.body === "string" ? init.body : void 0;
|
|
3215
|
+
const { authorization } = generateHmacAuthHeaders({
|
|
3216
|
+
secretKey: settings.hmacKey,
|
|
3217
|
+
method,
|
|
3218
|
+
path: routerPath,
|
|
3219
|
+
body: body || void 0,
|
|
3220
|
+
keyId: settings.instanceId
|
|
3221
|
+
});
|
|
3222
|
+
const headers = new Headers(init.headers);
|
|
3223
|
+
headers.set("authorization", authorization);
|
|
3224
|
+
const target = `${base.origin}${basePath}${INSTANCE_GATEWAY_MOUNT}${routerPath}`;
|
|
3225
|
+
const response = await fetchImpl(target, { ...init, method, headers });
|
|
3226
|
+
if (response.ok || (response.headers.get("content-type") ?? "").includes("json")) {
|
|
3227
|
+
return response;
|
|
3228
|
+
}
|
|
3229
|
+
const message = (await response.text().catch(() => "")).trim() || `Instance gateway answered ${response.status}`;
|
|
3230
|
+
return new Response(
|
|
3231
|
+
JSON.stringify({
|
|
3232
|
+
error: {
|
|
3233
|
+
type: "invalid_request_error",
|
|
3234
|
+
code: "forklaunch_gateway",
|
|
3235
|
+
message
|
|
3236
|
+
}
|
|
3237
|
+
}),
|
|
3238
|
+
{
|
|
3239
|
+
status: response.status,
|
|
3240
|
+
headers: {
|
|
3241
|
+
"content-type": "application/json",
|
|
3242
|
+
"stripe-should-retry": "false",
|
|
3243
|
+
...response.headers.get("retry-after") ? { "retry-after": response.headers.get("retry-after") } : {}
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
);
|
|
3247
|
+
};
|
|
3248
|
+
}
|
|
3249
|
+
function createStripeClient(options2 = {}) {
|
|
3250
|
+
const StripeClass = options2.Stripe ?? loadStripe();
|
|
3251
|
+
const explicitGateway = Boolean(
|
|
3252
|
+
options2.gatewayUrl || options2.instanceId || options2.hmacKey
|
|
3253
|
+
);
|
|
3254
|
+
if (!explicitGateway && !isManagedInstance()) {
|
|
3255
|
+
if (!options2.apiKey) {
|
|
3256
|
+
throw new Error(
|
|
3257
|
+
"createStripeClient: not a managed instance (PLATFORM_GATEWAY_URL, INSTANCE_ID and INSTANCE_HMAC_KEY are unset) and no apiKey was given \u2014 pass { apiKey } for local development, or run the gateway mock (forklaunch-gateway-mock) with the managed env"
|
|
3258
|
+
);
|
|
3259
|
+
}
|
|
3260
|
+
return new StripeClass(options2.apiKey, options2.config);
|
|
3261
|
+
}
|
|
3262
|
+
const settings = instanceGatewaySettings(options2, "Payments");
|
|
3263
|
+
const gateway = new URL(settings.gatewayUrl);
|
|
3264
|
+
const protocol = gateway.protocol === "http:" ? "http" : "https";
|
|
3265
|
+
return new StripeClass(
|
|
3266
|
+
// No key: the authenticator below replaces it, and the gateway holds the
|
|
3267
|
+
// real one. (stripe-node accepts an authenticator in place of a key.)
|
|
3268
|
+
void 0,
|
|
3269
|
+
{
|
|
3270
|
+
maxNetworkRetries: 2,
|
|
3271
|
+
...options2.config,
|
|
3272
|
+
host: gateway.hostname,
|
|
3273
|
+
port: gateway.port || (protocol === "http" ? 80 : 443),
|
|
3274
|
+
protocol,
|
|
3275
|
+
telemetry: false,
|
|
3276
|
+
// The signed fetch sets `authorization`; nothing else may.
|
|
3277
|
+
authenticator: async () => {
|
|
3278
|
+
},
|
|
3279
|
+
httpClient: StripeClass.createFetchHttpClient(
|
|
3280
|
+
stripeGatewayFetch(settings, options2.fetch)
|
|
3281
|
+
)
|
|
3282
|
+
}
|
|
3283
|
+
);
|
|
3284
|
+
}
|
|
3285
|
+
|
|
2477
3286
|
// src/http/guards/isPath.ts
|
|
2478
3287
|
function isPath(path) {
|
|
2479
3288
|
return path.startsWith("/");
|
|
@@ -4435,7 +5244,7 @@ function generateOpenApiSpecs(schemaValidator, serverUrls, serverDescriptions, a
|
|
|
4435
5244
|
}
|
|
4436
5245
|
|
|
4437
5246
|
// src/http/telemetry/auditLogger.ts
|
|
4438
|
-
var
|
|
5247
|
+
var import_crypto5 = require("crypto");
|
|
4439
5248
|
var AuditLogger = class {
|
|
4440
5249
|
_otel;
|
|
4441
5250
|
constructor(otel) {
|
|
@@ -4468,7 +5277,7 @@ var AuditLogger = class {
|
|
|
4468
5277
|
if (body === void 0 || body === null || body === "") {
|
|
4469
5278
|
return "";
|
|
4470
5279
|
}
|
|
4471
|
-
return (0,
|
|
5280
|
+
return (0, import_crypto5.createHash)("sha256").update(body).digest("hex");
|
|
4472
5281
|
}
|
|
4473
5282
|
};
|
|
4474
5283
|
|
|
@@ -4570,15 +5379,41 @@ var RateLimiter = class {
|
|
|
4570
5379
|
ATTR_HTTP_ROUTE,
|
|
4571
5380
|
ATTR_SERVICE_NAME,
|
|
4572
5381
|
AuditLogger,
|
|
5382
|
+
EMAIL_LIMITS,
|
|
5383
|
+
EmailRequestError,
|
|
4573
5384
|
ForklaunchExpressLikeApplication,
|
|
4574
5385
|
ForklaunchExpressLikeRouter,
|
|
4575
5386
|
HTTPStatuses,
|
|
5387
|
+
INSTANCE_GATEWAY_MOUNT,
|
|
5388
|
+
InstanceGatewayRequestError,
|
|
5389
|
+
ModelGatewayRequestError,
|
|
4576
5390
|
OPENAPI_DEFAULT_VERSION,
|
|
4577
5391
|
OpenTelemetryCollector,
|
|
5392
|
+
PLATFORM_EVENTS_PATH,
|
|
5393
|
+
PLATFORM_EVENT_KEY_ID,
|
|
5394
|
+
PaymentsRequestError,
|
|
4578
5395
|
PinoLogger,
|
|
5396
|
+
PlatformEventVerificationError,
|
|
4579
5397
|
RateLimiter,
|
|
5398
|
+
SMS_MAX_SEGMENTS,
|
|
5399
|
+
STRIPE_GATEWAY_ROUTE,
|
|
5400
|
+
SmsClient,
|
|
5401
|
+
SmsRequestError,
|
|
5402
|
+
SmsValidationError,
|
|
5403
|
+
VOICE_ATTRIBUTE_LIMITS,
|
|
5404
|
+
VoiceClient,
|
|
5405
|
+
VoiceRequestError,
|
|
5406
|
+
WhatsAppRequestError,
|
|
4580
5407
|
createContext,
|
|
5408
|
+
createEmailClient,
|
|
4581
5409
|
createHmacToken,
|
|
5410
|
+
createInstanceGatewayTransport,
|
|
5411
|
+
createModelGatewayClient,
|
|
5412
|
+
createPaymentsClient,
|
|
5413
|
+
createSmsClient,
|
|
5414
|
+
createStripeClient,
|
|
5415
|
+
createVoiceClient,
|
|
5416
|
+
createWhatsAppClient,
|
|
4582
5417
|
delete_,
|
|
4583
5418
|
discriminateAuthMethod,
|
|
4584
5419
|
discriminateBody,
|
|
@@ -4598,10 +5433,14 @@ var RateLimiter = class {
|
|
|
4598
5433
|
httpRequestsInFlightCounter,
|
|
4599
5434
|
httpRequestsTotalCounter,
|
|
4600
5435
|
httpServerDurationHistogram,
|
|
5436
|
+
instanceGatewaySettings,
|
|
4601
5437
|
isClientError,
|
|
5438
|
+
isE164,
|
|
5439
|
+
isEmailAddress,
|
|
4602
5440
|
isForklaunchRequest,
|
|
4603
5441
|
isForklaunchRouter,
|
|
4604
5442
|
isInformational,
|
|
5443
|
+
isManagedInstance,
|
|
4605
5444
|
isPortBound,
|
|
4606
5445
|
isRedirection,
|
|
4607
5446
|
isServerError,
|
|
@@ -4617,8 +5456,15 @@ var RateLimiter = class {
|
|
|
4617
5456
|
put,
|
|
4618
5457
|
recordMetric,
|
|
4619
5458
|
resetProcessHandlersForTesting,
|
|
5459
|
+
signPlatformEvent,
|
|
5460
|
+
smsSegments,
|
|
5461
|
+
stripeGatewayFetch,
|
|
4620
5462
|
trace,
|
|
4621
5463
|
typedAuthHandler,
|
|
4622
|
-
typedHandler
|
|
5464
|
+
typedHandler,
|
|
5465
|
+
validateEmailSendRequest,
|
|
5466
|
+
validateSmsSend,
|
|
5467
|
+
verifyPlatformEvent,
|
|
5468
|
+
voiceRequestProblem
|
|
4623
5469
|
});
|
|
4624
5470
|
//# sourceMappingURL=index.js.map
|