@forklaunch/core 2.1.1 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +27 -11
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The instance gateway: how a managed instance reaches platform-held services
|
|
3
|
+
* (models, payments, email, SMS, WhatsApp, voice) without holding a vendor
|
|
4
|
+
* credential, and how the platform delivers vendor events back to it.
|
|
5
|
+
*
|
|
6
|
+
* Outbound, every request is signed with the instance's own HMAC key
|
|
7
|
+
* (`INSTANCE_HMAC_KEY`, key id `INSTANCE_ID`) over the router-relative path,
|
|
8
|
+
* with a timestamp and a single-use nonce; the platform checks the signature,
|
|
9
|
+
* the product's limits and the instance's own vendor account, then calls the
|
|
10
|
+
* vendor with credentials that never leave it.
|
|
11
|
+
*
|
|
12
|
+
* Inbound, the platform POSTs events (a payment succeeded, an email bounced, a
|
|
13
|
+
* text arrived) to `/platform-events/<feature>` on the service that declared
|
|
14
|
+
* the capability, signed with the same instance key under key id `platform`.
|
|
15
|
+
* `verifyPlatformEvent` checks that signature, the timestamp and the nonce.
|
|
16
|
+
*
|
|
17
|
+
* The platform injects `PLATFORM_GATEWAY_URL`, `INSTANCE_ID` and
|
|
18
|
+
* `INSTANCE_HMAC_KEY` into a hosted instance. Their absence means the service
|
|
19
|
+
* is not running in managed mode, and a client refuses to be created rather
|
|
20
|
+
* than failing on its first call.
|
|
21
|
+
*/
|
|
22
|
+
/** Where the gateway router is mounted on the platform service. */
|
|
23
|
+
export declare const INSTANCE_GATEWAY_MOUNT = "/instance-gateway";
|
|
24
|
+
/** The key id the platform signs events with. */
|
|
25
|
+
export declare const PLATFORM_EVENT_KEY_ID = "platform";
|
|
26
|
+
/** Where a service receives platform events, per feature. */
|
|
27
|
+
export declare const PLATFORM_EVENTS_PATH = "/platform-events";
|
|
28
|
+
export interface InstanceGatewayOptions {
|
|
29
|
+
/** Platform base URL. Default: `PLATFORM_GATEWAY_URL`. */
|
|
30
|
+
gatewayUrl?: string;
|
|
31
|
+
/** This instance's id. Default: `INSTANCE_ID`. */
|
|
32
|
+
instanceId?: string;
|
|
33
|
+
/** This instance's HMAC key. Default: `INSTANCE_HMAC_KEY`. */
|
|
34
|
+
hmacKey?: string;
|
|
35
|
+
fetch?: typeof fetch;
|
|
36
|
+
}
|
|
37
|
+
/** A refusal or failure from the gateway, with the status it answered. */
|
|
38
|
+
export declare class InstanceGatewayRequestError extends Error {
|
|
39
|
+
readonly status: number;
|
|
40
|
+
/** Seconds to wait before retrying, on a 429. */
|
|
41
|
+
readonly retryAfterSeconds?: number | undefined;
|
|
42
|
+
readonly name: string;
|
|
43
|
+
constructor(status: number, message: string,
|
|
44
|
+
/** Seconds to wait before retrying, on a 429. */
|
|
45
|
+
retryAfterSeconds?: number | undefined);
|
|
46
|
+
}
|
|
47
|
+
export interface InstanceGatewayTransport {
|
|
48
|
+
readonly instanceId: string;
|
|
49
|
+
/** A signed request; throws `InstanceGatewayRequestError` on a non-2xx. */
|
|
50
|
+
request(method: 'GET' | 'POST' | 'PUT' | 'DELETE', route: string, body?: Record<string, unknown>, options?: {
|
|
51
|
+
signal?: AbortSignal;
|
|
52
|
+
headers?: Record<string, string>;
|
|
53
|
+
}): Promise<Response>;
|
|
54
|
+
/** `request`, then the JSON body. */
|
|
55
|
+
json<T>(method: 'GET' | 'POST' | 'PUT' | 'DELETE', route: string, body?: Record<string, unknown>, options?: {
|
|
56
|
+
signal?: AbortSignal;
|
|
57
|
+
headers?: Record<string, string>;
|
|
58
|
+
}): Promise<T>;
|
|
59
|
+
}
|
|
60
|
+
/** Resolve the managed-mode settings, or explain which are missing. */
|
|
61
|
+
export declare function instanceGatewaySettings(options?: InstanceGatewayOptions, feature?: string): {
|
|
62
|
+
gatewayUrl: string;
|
|
63
|
+
instanceId: string;
|
|
64
|
+
hmacKey: string;
|
|
65
|
+
};
|
|
66
|
+
/** Whether this process runs as a managed instance. */
|
|
67
|
+
export declare function isManagedInstance(env?: Record<string, string | undefined>): boolean;
|
|
68
|
+
/** A signed transport to the instance gateway. */
|
|
69
|
+
export declare function createInstanceGatewayTransport(options?: InstanceGatewayOptions, feature?: string): InstanceGatewayTransport;
|
|
70
|
+
/** An event the platform delivered to this instance. */
|
|
71
|
+
export interface PlatformEvent<Data = Record<string, unknown>> {
|
|
72
|
+
/** Unique per event; deliveries can repeat, so handlers dedupe on it. */
|
|
73
|
+
id: string;
|
|
74
|
+
/** The capability it belongs to: payments, email, sms, whatsapp, voice. */
|
|
75
|
+
feature: string;
|
|
76
|
+
/** e.g. `checkout.session.completed`, `email.bounced`, `sms.received`. */
|
|
77
|
+
type: string;
|
|
78
|
+
occurredAt: string;
|
|
79
|
+
data: Data;
|
|
80
|
+
}
|
|
81
|
+
export declare class PlatformEventVerificationError extends Error {
|
|
82
|
+
readonly name = "PlatformEventVerificationError";
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Verify an event the platform delivered and return it.
|
|
86
|
+
*
|
|
87
|
+
* `path` is the path the request arrived on (`/platform-events/payments`);
|
|
88
|
+
* `rawBody` is the exact request body (a string or Buffer) — pass the raw
|
|
89
|
+
* bytes when the framework captured them, else the parsed body. Throws
|
|
90
|
+
* `PlatformEventVerificationError` for a missing, stale, forged or replayed
|
|
91
|
+
* delivery; answer 401 then, and the platform retries.
|
|
92
|
+
*/
|
|
93
|
+
export declare function verifyPlatformEvent<Data = Record<string, unknown>>(request: {
|
|
94
|
+
method: string;
|
|
95
|
+
path: string;
|
|
96
|
+
headers: Record<string, string | string[] | undefined>;
|
|
97
|
+
body: unknown;
|
|
98
|
+
}, options?: {
|
|
99
|
+
hmacKey?: string;
|
|
100
|
+
now?: () => number;
|
|
101
|
+
}): PlatformEvent<Data>;
|
|
102
|
+
/**
|
|
103
|
+
* Sign an event delivery the way the platform does. For tests and the local
|
|
104
|
+
* gateway mock; the platform has its own copy.
|
|
105
|
+
*/
|
|
106
|
+
export declare function signPlatformEvent(params: {
|
|
107
|
+
hmacKey: string;
|
|
108
|
+
path: string;
|
|
109
|
+
body: string;
|
|
110
|
+
}): {
|
|
111
|
+
authorization: string;
|
|
112
|
+
};
|
|
113
|
+
//# sourceMappingURL=instanceGateway.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instanceGateway.d.ts","sourceRoot":"","sources":["../../src/http/instanceGateway.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { InstanceGatewayRequestError } from './instanceGateway';
|
|
2
|
+
/**
|
|
3
|
+
* Client for the ForkLaunch model gateway, used from inside a managed
|
|
4
|
+
* instance.
|
|
5
|
+
*
|
|
6
|
+
* A managed instance calls platform-hosted AI models (Azure AI Foundry)
|
|
7
|
+
* without holding a provider key: each request is signed with the
|
|
8
|
+
* instance's own HMAC key, and the platform enforces the product's model
|
|
9
|
+
* allowlist, monthly token budget and rate limit. For a HIPAA product the
|
|
10
|
+
* platform only offers models covered by its BAA.
|
|
11
|
+
*
|
|
12
|
+
* The platform injects everything this needs into a hosted instance:
|
|
13
|
+
* `PLATFORM_GATEWAY_URL` (the platform's public base), `INSTANCE_ID` and
|
|
14
|
+
* `INSTANCE_HMAC_KEY`. Their absence means the instance is not hosted in
|
|
15
|
+
* managed mode, and the client refuses to be created rather than failing on
|
|
16
|
+
* the first call.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* const models = createModelGatewayClient();
|
|
20
|
+
* const reply = await models.chat.completions.create({
|
|
21
|
+
* model: 'terra',
|
|
22
|
+
* messages: [{ role: 'user', content: 'Summarize this intake form: …' }]
|
|
23
|
+
* });
|
|
24
|
+
* for await (const chunk of models.chat.completions.stream({ model: 'luna', messages })) {
|
|
25
|
+
* process.stdout.write(chunk.choices[0]?.delta?.content ?? '');
|
|
26
|
+
* }
|
|
27
|
+
*/
|
|
28
|
+
export interface ModelGatewayClientOptions {
|
|
29
|
+
/** Platform base URL. Default: `PLATFORM_GATEWAY_URL`. */
|
|
30
|
+
gatewayUrl?: string;
|
|
31
|
+
/** This instance's id. Default: `INSTANCE_ID`. */
|
|
32
|
+
instanceId?: string;
|
|
33
|
+
/** This instance's HMAC key. Default: `INSTANCE_HMAC_KEY`. */
|
|
34
|
+
hmacKey?: string;
|
|
35
|
+
fetch?: typeof fetch;
|
|
36
|
+
}
|
|
37
|
+
/** An OpenAI-style chat message. */
|
|
38
|
+
export interface GatewayChatMessage {
|
|
39
|
+
role: 'system' | 'developer' | 'user' | 'assistant' | 'tool';
|
|
40
|
+
content: unknown;
|
|
41
|
+
[key: string]: unknown;
|
|
42
|
+
}
|
|
43
|
+
/** An OpenAI-compatible chat completions request; `model` is a catalog alias. */
|
|
44
|
+
export interface GatewayChatRequest {
|
|
45
|
+
model: string;
|
|
46
|
+
messages: GatewayChatMessage[];
|
|
47
|
+
temperature?: number;
|
|
48
|
+
top_p?: number;
|
|
49
|
+
max_tokens?: number;
|
|
50
|
+
max_completion_tokens?: number;
|
|
51
|
+
response_format?: unknown;
|
|
52
|
+
tools?: unknown[];
|
|
53
|
+
tool_choice?: unknown;
|
|
54
|
+
parallel_tool_calls?: boolean;
|
|
55
|
+
stop?: unknown;
|
|
56
|
+
seed?: number;
|
|
57
|
+
presence_penalty?: number;
|
|
58
|
+
frequency_penalty?: number;
|
|
59
|
+
reasoning_effort?: string;
|
|
60
|
+
}
|
|
61
|
+
export interface GatewayChatCompletion {
|
|
62
|
+
id: string;
|
|
63
|
+
object: string;
|
|
64
|
+
created: number;
|
|
65
|
+
model: string;
|
|
66
|
+
choices: {
|
|
67
|
+
index: number;
|
|
68
|
+
message: {
|
|
69
|
+
role: string;
|
|
70
|
+
content: string | null;
|
|
71
|
+
[key: string]: unknown;
|
|
72
|
+
};
|
|
73
|
+
finish_reason: string | null;
|
|
74
|
+
[key: string]: unknown;
|
|
75
|
+
}[];
|
|
76
|
+
usage?: {
|
|
77
|
+
prompt_tokens: number;
|
|
78
|
+
completion_tokens: number;
|
|
79
|
+
total_tokens: number;
|
|
80
|
+
};
|
|
81
|
+
[key: string]: unknown;
|
|
82
|
+
}
|
|
83
|
+
export interface GatewayChatChunk {
|
|
84
|
+
id: string;
|
|
85
|
+
object: string;
|
|
86
|
+
model: string;
|
|
87
|
+
choices: {
|
|
88
|
+
index: number;
|
|
89
|
+
delta: {
|
|
90
|
+
role?: string;
|
|
91
|
+
content?: string | null;
|
|
92
|
+
[key: string]: unknown;
|
|
93
|
+
};
|
|
94
|
+
finish_reason?: string | null;
|
|
95
|
+
}[];
|
|
96
|
+
usage?: GatewayChatCompletion['usage'];
|
|
97
|
+
[key: string]: unknown;
|
|
98
|
+
}
|
|
99
|
+
export interface GatewayModels {
|
|
100
|
+
models: {
|
|
101
|
+
id: string;
|
|
102
|
+
baa: boolean;
|
|
103
|
+
}[];
|
|
104
|
+
monthlyTokenBudget: number;
|
|
105
|
+
tokensUsedThisMonth: number;
|
|
106
|
+
requestsPerMinute: number;
|
|
107
|
+
withheldForHipaa?: string[];
|
|
108
|
+
}
|
|
109
|
+
/** A refusal or failure from the gateway, with the status it answered. */
|
|
110
|
+
export declare class ModelGatewayRequestError extends InstanceGatewayRequestError {
|
|
111
|
+
readonly name: 'ModelGatewayRequestError';
|
|
112
|
+
}
|
|
113
|
+
export interface ModelGatewayClient {
|
|
114
|
+
/** The models this instance may call, and where its monthly budget stands. */
|
|
115
|
+
models(): Promise<GatewayModels>;
|
|
116
|
+
chat: {
|
|
117
|
+
completions: {
|
|
118
|
+
create(request: GatewayChatRequest): Promise<GatewayChatCompletion>;
|
|
119
|
+
stream(request: GatewayChatRequest, options?: {
|
|
120
|
+
signal?: AbortSignal;
|
|
121
|
+
}): AsyncGenerator<GatewayChatChunk>;
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
export declare function createModelGatewayClient(options?: ModelGatewayClientOptions): ModelGatewayClient;
|
|
126
|
+
//# sourceMappingURL=modelGatewayClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modelGatewayClient.d.ts","sourceRoot":"","sources":["../../src/http/modelGatewayClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type Stripe from 'stripe';
|
|
2
|
+
import { InstanceGatewayRequestError, type InstanceGatewayOptions } from './instanceGateway';
|
|
3
|
+
/**
|
|
4
|
+
* Payments for a managed instance: Stripe Connect through the platform.
|
|
5
|
+
*
|
|
6
|
+
* The product's publisher runs the Stripe Connect platform, linked once to
|
|
7
|
+
* ForkLaunch. Each managed instance (a clinic, a studio) gets its OWN
|
|
8
|
+
* connected account and is the merchant of record: charges are direct
|
|
9
|
+
* charges on that account, and payouts go to the clinic's bank, never through
|
|
10
|
+
* the app. The app never holds a Stripe key:
|
|
11
|
+
*
|
|
12
|
+
* - `createPaymentsClient()` onboards the instance (a Stripe-hosted Account
|
|
13
|
+
* Link) and reports whether it can take payments yet;
|
|
14
|
+
* - `createStripeClient()` is the REAL Stripe SDK, pointed at the instance
|
|
15
|
+
* gateway. Every request is signed with the instance key, and the platform
|
|
16
|
+
* pins `Stripe-Account` to this instance's connected account, forwards
|
|
17
|
+
* only an allowlist of calls (customers, products and prices, checkout and
|
|
18
|
+
* billing-portal sessions, payment links, subscriptions, payment intents,
|
|
19
|
+
* refunds, invoices — never payouts, transfers, external accounts or
|
|
20
|
+
* account changes), adds the product's application fee, and refuses
|
|
21
|
+
* metadata that looks like protected data;
|
|
22
|
+
* - Stripe's events arrive as platform events (`/platform-events/payments`,
|
|
23
|
+
* type = the Stripe event type, id = the Stripe event id, data = the
|
|
24
|
+
* event's object), verified with `verifyPlatformEvent`.
|
|
25
|
+
*
|
|
26
|
+
* Outside managed mode `createStripeClient({ apiKey })` is `new Stripe(apiKey)`,
|
|
27
|
+
* so the same registration runs against a developer's own test account.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* const payments = createPaymentsClient();
|
|
31
|
+
* const { url } = await payments.onboardingLink({
|
|
32
|
+
* returnUrl: 'https://clinic.example.com/settings/payments?done=1',
|
|
33
|
+
* refreshUrl: 'https://clinic.example.com/settings/payments'
|
|
34
|
+
* });
|
|
35
|
+
* const stripe = createStripeClient({ Stripe });
|
|
36
|
+
* const session = await stripe.checkout.sessions.create({ mode: 'payment', … });
|
|
37
|
+
*/
|
|
38
|
+
/** Where the gateway proxies Stripe's API, router-relative. */
|
|
39
|
+
export declare const STRIPE_GATEWAY_ROUTE = "/stripe";
|
|
40
|
+
export interface PaymentsStatus {
|
|
41
|
+
/** The connected account can take card payments. */
|
|
42
|
+
chargesEnabled: boolean;
|
|
43
|
+
/** Stripe can pay out to the clinic's bank account. */
|
|
44
|
+
payoutsEnabled: boolean;
|
|
45
|
+
/** What Stripe still needs (`individual.verification.document`, …). */
|
|
46
|
+
requirementsDue: string[];
|
|
47
|
+
}
|
|
48
|
+
export interface PaymentsClient {
|
|
49
|
+
/**
|
|
50
|
+
* A single-use Stripe-hosted onboarding link for this instance's connected
|
|
51
|
+
* account (created on first use). Send the clinic's owner to `url`; Stripe
|
|
52
|
+
* returns them to `returnUrl`, or to `refreshUrl` when the link expired.
|
|
53
|
+
*/
|
|
54
|
+
onboardingLink(params: {
|
|
55
|
+
returnUrl: string;
|
|
56
|
+
refreshUrl: string;
|
|
57
|
+
}): Promise<{
|
|
58
|
+
url: string;
|
|
59
|
+
}>;
|
|
60
|
+
/** Whether the instance can take payments yet. */
|
|
61
|
+
status(): Promise<PaymentsStatus>;
|
|
62
|
+
}
|
|
63
|
+
/** A refusal or failure from the payments gateway. */
|
|
64
|
+
export declare class PaymentsRequestError extends InstanceGatewayRequestError {
|
|
65
|
+
readonly name: 'PaymentsRequestError';
|
|
66
|
+
}
|
|
67
|
+
export declare function createPaymentsClient(options?: InstanceGatewayOptions): PaymentsClient;
|
|
68
|
+
type StripeConstructor = typeof Stripe;
|
|
69
|
+
export interface StripeClientOptions extends InstanceGatewayOptions {
|
|
70
|
+
/**
|
|
71
|
+
* A Stripe secret key, for a service NOT running in managed mode (local
|
|
72
|
+
* development against your own test account, a self-hosted deployment).
|
|
73
|
+
* Ignored in managed mode, where the platform holds the key.
|
|
74
|
+
*/
|
|
75
|
+
apiKey?: string;
|
|
76
|
+
/**
|
|
77
|
+
* The `Stripe` class to construct. Pass your own `import Stripe from
|
|
78
|
+
* 'stripe'` when the client is registered with `type: Stripe`: an ESM
|
|
79
|
+
* service and this package could otherwise load different builds of the
|
|
80
|
+
* SDK, and the registration's `instanceof` check would fail. Default: the
|
|
81
|
+
* `stripe` package, loaded on first use (it is an optional peer dependency).
|
|
82
|
+
*/
|
|
83
|
+
Stripe?: StripeConstructor;
|
|
84
|
+
/** Extra SDK settings (apiVersion, maxNetworkRetries, timeout, appInfo). */
|
|
85
|
+
config?: Stripe.StripeConfig;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* A fetch that sends Stripe SDK requests to the instance gateway, signed.
|
|
89
|
+
*
|
|
90
|
+
* The SDK builds `https://<host>:<port>/v1/...` from the `host`/`port`/
|
|
91
|
+
* `protocol` settings; this rewrites the path under
|
|
92
|
+
* `/instance-gateway/stripe` and adds the instance's HMAC `authorization`,
|
|
93
|
+
* replacing any bearer key. The signature covers the method, the
|
|
94
|
+
* router-relative path WITH its query string (`/stripe/v1/prices?active=true`,
|
|
95
|
+
* so a list filter cannot be altered in flight) and the body exactly as sent:
|
|
96
|
+
* Stripe's bodies are form-encoded, and the raw form string is signed
|
|
97
|
+
* verbatim (framework `createHmacToken` treats a string body as the wire
|
|
98
|
+
* bytes). An empty body signs as no body.
|
|
99
|
+
*/
|
|
100
|
+
export declare function stripeGatewayFetch(settings: {
|
|
101
|
+
gatewayUrl: string;
|
|
102
|
+
instanceId: string;
|
|
103
|
+
hmacKey: string;
|
|
104
|
+
}, fetchImpl?: typeof fetch): typeof fetch;
|
|
105
|
+
/**
|
|
106
|
+
* The Stripe SDK for this service.
|
|
107
|
+
*
|
|
108
|
+
* In managed mode (gateway options given, or `PLATFORM_GATEWAY_URL`,
|
|
109
|
+
* `INSTANCE_ID` and `INSTANCE_HMAC_KEY` set) it is a real `Stripe` whose
|
|
110
|
+
* requests go to the platform's instance gateway, signed with the instance
|
|
111
|
+
* key; there is no Stripe key in the app. Otherwise it is `new
|
|
112
|
+
* Stripe(apiKey)`.
|
|
113
|
+
*
|
|
114
|
+
* Mechanism (stripe-node 22): `host`/`port`/`protocol` point the SDK at the
|
|
115
|
+
* gateway, `httpClient: Stripe.createFetchHttpClient(signedFetch)` rewrites
|
|
116
|
+
* each request under `/instance-gateway/stripe` and signs it (see
|
|
117
|
+
* `stripeGatewayFetch`), and a no-op `authenticator` stands in for the API
|
|
118
|
+
* key so no bearer credential is ever sent. Retries re-enter the fetch, so
|
|
119
|
+
* each attempt carries a fresh nonce.
|
|
120
|
+
*/
|
|
121
|
+
export declare function createStripeClient(options?: StripeClientOptions): Stripe;
|
|
122
|
+
export {};
|
|
123
|
+
//# sourceMappingURL=paymentsClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paymentsClient.d.ts","sourceRoot":"","sources":["../../src/http/paymentsClient.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,EAGL,2BAA2B,EAG3B,KAAK,sBAAsB,EAC5B,MAAM,mBAAmB,CAAC;AAG3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,+DAA+D;AAC/D,eAAO,MAAM,oBAAoB,YAAY,CAAC;AAE9C,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,cAAc,EAAE,OAAO,CAAC;IACxB,uDAAuD;IACvD,cAAc,EAAE,OAAO,CAAC;IACxB,uEAAuE;IACvE,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,cAAc,CAAC,MAAM,EAAE;QACrB,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7B,kDAAkD;IAClD,MAAM,IAAI,OAAO,CAAC,cAAc,CAAC,CAAC;CACnC;AAED,sDAAsD;AACtD,qBAAa,oBAAqB,SAAQ,2BAA2B;IACnE,SAAkB,IAAI,EAAG,sBAAsB,CAAU;CAC1D;AAED,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,sBAA2B,GACnC,cAAc,CAuChB;AAED,KAAK,iBAAiB,GAAG,OAAO,MAAM,CAAC;AAEvC,MAAM,WAAW,mBAAoB,SAAQ,sBAAsB;IACjE;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAC,YAAY,CAAC;CAC9B;AAsBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EACrE,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,KAAK,CAqDd;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,MAAM,CAoC5E"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { InstanceGatewayRequestError, type InstanceGatewayOptions, type PlatformEvent } from './instanceGateway';
|
|
2
|
+
/**
|
|
3
|
+
* Client for platform-held SMS (AWS End User Messaging SMS), used from inside
|
|
4
|
+
* a managed instance.
|
|
5
|
+
*
|
|
6
|
+
* The instance never holds an AWS credential or a phone number. It signs each
|
|
7
|
+
* send with its own HMAC key; the platform checks the instance's monthly
|
|
8
|
+
* segment cap, its rate limit and its opt-out list, then sends from the
|
|
9
|
+
* product's origination pool under a configuration set named for the
|
|
10
|
+
* instance, so delivery receipts come back to this instance and no other.
|
|
11
|
+
*
|
|
12
|
+
* Events arrive at `POST /platform-events/sms` (verify them with
|
|
13
|
+
* `verifyPlatformEvent`): `sms.delivered`, `sms.failed`, `sms.received`
|
|
14
|
+
* (a reply) and `sms.opted_out` (the person texted STOP; further sends to that
|
|
15
|
+
* number are refused with 422).
|
|
16
|
+
*
|
|
17
|
+
* A text message is not a secure channel: carriers, the handset's lock screen
|
|
18
|
+
* and anyone holding the phone can read it. Never put protected data (a
|
|
19
|
+
* `.deanon` value, a diagnosis, a result) in `body`; send a link to sign in
|
|
20
|
+
* instead. `forklaunch score` flags it (`sms-protected-data`).
|
|
21
|
+
*
|
|
22
|
+
* Locally, run the gateway mock (`npx -p @forklaunch/core
|
|
23
|
+
* forklaunch-gateway-mock`, or the `gateway-mock` compose service that
|
|
24
|
+
* `forklaunch infra add <service> sms` wires) and point PLATFORM_GATEWAY_URL,
|
|
25
|
+
* INSTANCE_ID and INSTANCE_HMAC_KEY at it.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* const sms = createSmsClient();
|
|
29
|
+
* const { messageId, segments } = await sms.send({
|
|
30
|
+
* to: '+14155550123',
|
|
31
|
+
* body: 'Your appointment is confirmed. Sign in for details: https://…',
|
|
32
|
+
* purpose: 'transactional'
|
|
33
|
+
* });
|
|
34
|
+
*/
|
|
35
|
+
export interface SmsClientOptions extends InstanceGatewayOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Refuse on the first `send` rather than at creation when the service is
|
|
38
|
+
* not a managed instance. For dependency containers that build singletons
|
|
39
|
+
* at boot, so a local run without the gateway still starts.
|
|
40
|
+
*/
|
|
41
|
+
deferRefusal?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export type SmsPurpose = 'transactional' | 'promotional';
|
|
44
|
+
export interface SmsSendRequest {
|
|
45
|
+
/** E.164, e.g. `+14155550123`. */
|
|
46
|
+
to: string;
|
|
47
|
+
/** Plain text. At most `SMS_MAX_SEGMENTS` segments. */
|
|
48
|
+
body: string;
|
|
49
|
+
/** Default `transactional`. Promotional texts need prior marketing consent. */
|
|
50
|
+
purpose?: SmsPurpose;
|
|
51
|
+
}
|
|
52
|
+
export interface SmsSendResult {
|
|
53
|
+
/** The vendor's message id; delivery events carry it. */
|
|
54
|
+
messageId: string;
|
|
55
|
+
/** Billed segments (a long or non-GSM message is several). */
|
|
56
|
+
segments: number;
|
|
57
|
+
}
|
|
58
|
+
export interface SmsDeliveredData {
|
|
59
|
+
messageId: string;
|
|
60
|
+
to: string;
|
|
61
|
+
deliveredAt?: string;
|
|
62
|
+
}
|
|
63
|
+
export interface SmsFailedData {
|
|
64
|
+
messageId: string;
|
|
65
|
+
to: string;
|
|
66
|
+
reason: string;
|
|
67
|
+
}
|
|
68
|
+
export interface SmsReceivedData {
|
|
69
|
+
from: string;
|
|
70
|
+
body: string;
|
|
71
|
+
receivedAt: string;
|
|
72
|
+
/** The recognized keyword (STOP, HELP, START…), when the text was one. */
|
|
73
|
+
keyword?: string;
|
|
74
|
+
}
|
|
75
|
+
export interface SmsOptedOutData {
|
|
76
|
+
phone: string;
|
|
77
|
+
optedOutAt: string;
|
|
78
|
+
}
|
|
79
|
+
/** The events the platform delivers for SMS. */
|
|
80
|
+
export type SmsEvent = (PlatformEvent<SmsDeliveredData> & {
|
|
81
|
+
type: 'sms.delivered';
|
|
82
|
+
}) | (PlatformEvent<SmsFailedData> & {
|
|
83
|
+
type: 'sms.failed';
|
|
84
|
+
}) | (PlatformEvent<SmsReceivedData> & {
|
|
85
|
+
type: 'sms.received';
|
|
86
|
+
}) | (PlatformEvent<SmsOptedOutData> & {
|
|
87
|
+
type: 'sms.opted_out';
|
|
88
|
+
});
|
|
89
|
+
/** A refusal or failure from the SMS gateway, with the status it answered. */
|
|
90
|
+
export declare class SmsRequestError extends InstanceGatewayRequestError {
|
|
91
|
+
readonly name: 'SmsRequestError';
|
|
92
|
+
}
|
|
93
|
+
/** An input the client refuses before any network call. */
|
|
94
|
+
export declare class SmsValidationError extends Error {
|
|
95
|
+
readonly name: 'SmsValidationError';
|
|
96
|
+
}
|
|
97
|
+
export declare const SMS_MAX_SEGMENTS = 10;
|
|
98
|
+
export declare function isE164(phone: string): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* How many segments a body is billed as: GSM-7 is 160 septets in one
|
|
101
|
+
* segment, 153 per segment when split; anything outside GSM-7 is UCS-2, 70
|
|
102
|
+
* characters in one, 67 per segment (UTF-16 code units).
|
|
103
|
+
*/
|
|
104
|
+
export declare function smsSegments(body: string): {
|
|
105
|
+
encoding: 'GSM-7' | 'UCS-2';
|
|
106
|
+
units: number;
|
|
107
|
+
segments: number;
|
|
108
|
+
};
|
|
109
|
+
/** Check a send before it leaves the process; throws SmsValidationError. */
|
|
110
|
+
export declare function validateSmsSend(request: SmsSendRequest): void;
|
|
111
|
+
/**
|
|
112
|
+
* Sends texts through the platform. A class so a dependency container can
|
|
113
|
+
* register it by type (`type: SmsClient`); build it with `createSmsClient`.
|
|
114
|
+
*/
|
|
115
|
+
export declare class SmsClient {
|
|
116
|
+
private readonly transport?;
|
|
117
|
+
constructor(options?: SmsClientOptions);
|
|
118
|
+
/** Whether sends can reach the gateway (false: every send refuses). */
|
|
119
|
+
get managed(): boolean;
|
|
120
|
+
send(request: SmsSendRequest): Promise<SmsSendResult>;
|
|
121
|
+
}
|
|
122
|
+
export declare function createSmsClient(options?: SmsClientOptions): SmsClient;
|
|
123
|
+
//# sourceMappingURL=smsClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smsClient.d.ts","sourceRoot":"","sources":["../../src/http/smsClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { type InstanceGatewayOptions, InstanceGatewayRequestError } from './instanceGateway';
|
|
2
|
+
/**
|
|
3
|
+
* Client for platform-held voice calls (Amazon Connect), used from inside a
|
|
4
|
+
* managed instance.
|
|
5
|
+
*
|
|
6
|
+
* The instance never holds an AWS credential, a Connect instance id or a
|
|
7
|
+
* contact-flow ARN. It asks the platform to place a call with a FLOW NAME from
|
|
8
|
+
* the product's flow catalog (`appointment_reminder`, …); the platform maps it
|
|
9
|
+
* to the contact flow, dials from the phone number claimed for this instance,
|
|
10
|
+
* and enforces the instance's concurrent-call and monthly-minute limits.
|
|
11
|
+
*
|
|
12
|
+
* Call progress arrives as platform events on `/platform-events/voice`
|
|
13
|
+
* (`voice.call.started`, `voice.call.ended`, `voice.recording.ready`), signed
|
|
14
|
+
* with the instance key; `forklaunch infra add <service> voice` writes the
|
|
15
|
+
* route and a handler stub.
|
|
16
|
+
*
|
|
17
|
+
* Attributes are passed to the contact flow and are stored in Connect's
|
|
18
|
+
* contact records. Put identifiers in them (an appointment id the flow looks
|
|
19
|
+
* up), never health information: the `voice-protected-data` check flags a
|
|
20
|
+
* `.deanon` value flowing into them.
|
|
21
|
+
*
|
|
22
|
+
* Locally, `infra add` points the service at the gateway mock in
|
|
23
|
+
* docker-compose (flows from `MOCK_VOICE_FLOWS`, events delivered back to the
|
|
24
|
+
* service), so the same code runs with no AWS account.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const voice = createVoiceClient();
|
|
28
|
+
* const { callId } = await voice.startOutboundCall({
|
|
29
|
+
* to: '+15551230000',
|
|
30
|
+
* flow: 'appointment_reminder',
|
|
31
|
+
* attributes: { appointmentId: appt.id }
|
|
32
|
+
* });
|
|
33
|
+
* const { status } = await voice.call(callId);
|
|
34
|
+
*/
|
|
35
|
+
export type VoiceClientOptions = InstanceGatewayOptions;
|
|
36
|
+
/** Limits the gateway enforces on attributes; the client checks them first. */
|
|
37
|
+
export declare const VOICE_ATTRIBUTE_LIMITS: {
|
|
38
|
+
readonly maxKeys: 20;
|
|
39
|
+
readonly maxKeyLength: 64;
|
|
40
|
+
readonly maxValueLength: 256;
|
|
41
|
+
readonly maxTotalBytes: 4096;
|
|
42
|
+
/** Keys the platform reserves (it prefixes and adds its own under `fl_`). */
|
|
43
|
+
readonly reservedPrefix: 'fl_';
|
|
44
|
+
};
|
|
45
|
+
export interface StartOutboundCallRequest {
|
|
46
|
+
/** The number to call, E.164 (`+15551230000`). */
|
|
47
|
+
to: string;
|
|
48
|
+
/** A flow name from the product's catalog, e.g. `appointment_reminder`. */
|
|
49
|
+
flow: string;
|
|
50
|
+
/** String values the contact flow can read. No health information. */
|
|
51
|
+
attributes?: Record<string, string>;
|
|
52
|
+
}
|
|
53
|
+
export interface StartOutboundCallResult {
|
|
54
|
+
callId: string;
|
|
55
|
+
}
|
|
56
|
+
export type VoiceCallStatus = 'initiated' | 'in_progress' | 'ended';
|
|
57
|
+
export interface VoiceCall {
|
|
58
|
+
status: VoiceCallStatus;
|
|
59
|
+
startedAt: string;
|
|
60
|
+
endedAt?: string;
|
|
61
|
+
durationSeconds?: number;
|
|
62
|
+
/** Why it ended: `customer`, `agent`, `api`, `busy`, `no_answer`, … */
|
|
63
|
+
disconnectReason?: string;
|
|
64
|
+
}
|
|
65
|
+
/** Data of `voice.call.started`. */
|
|
66
|
+
export interface VoiceCallStartedEvent {
|
|
67
|
+
callId: string;
|
|
68
|
+
flow?: string;
|
|
69
|
+
}
|
|
70
|
+
/** Data of `voice.call.ended`. */
|
|
71
|
+
export interface VoiceCallEndedEvent {
|
|
72
|
+
callId: string;
|
|
73
|
+
durationSeconds: number;
|
|
74
|
+
disconnectReason: string;
|
|
75
|
+
}
|
|
76
|
+
/** Data of `voice.recording.ready`: the key in the instance's object store. */
|
|
77
|
+
export interface VoiceRecordingReadyEvent {
|
|
78
|
+
callId: string;
|
|
79
|
+
recordingKey: string;
|
|
80
|
+
}
|
|
81
|
+
/** A refusal or failure from the voice gateway, with its status. */
|
|
82
|
+
export declare class VoiceRequestError extends InstanceGatewayRequestError {
|
|
83
|
+
readonly name: 'VoiceRequestError';
|
|
84
|
+
}
|
|
85
|
+
/** Why a request would be refused, or undefined. Shared with the gateway. */
|
|
86
|
+
export declare function voiceRequestProblem(request: StartOutboundCallRequest): string | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* The voice client. A class so a service's registrations.ts can declare it
|
|
89
|
+
* (`type: VoiceClient`); construct it with `createVoiceClient()`.
|
|
90
|
+
*/
|
|
91
|
+
export declare class VoiceClient {
|
|
92
|
+
private readonly transport;
|
|
93
|
+
/** Throws when the process is not a managed instance (see instanceGatewaySettings). */
|
|
94
|
+
constructor(options?: VoiceClientOptions);
|
|
95
|
+
/** Place an outbound call; resolves once the platform accepted it. */
|
|
96
|
+
startOutboundCall(request: StartOutboundCallRequest): Promise<StartOutboundCallResult>;
|
|
97
|
+
/** Hang up a call this instance placed. */
|
|
98
|
+
endCall(callId: string): Promise<void>;
|
|
99
|
+
/** Where a call this instance placed stands. */
|
|
100
|
+
call(callId: string): Promise<VoiceCall>;
|
|
101
|
+
private send;
|
|
102
|
+
}
|
|
103
|
+
/** A voice client for this managed instance (settings from the environment). */
|
|
104
|
+
export declare function createVoiceClient(options?: VoiceClientOptions): VoiceClient;
|
|
105
|
+
//# sourceMappingURL=voiceClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voiceClient.d.ts","sourceRoot":"","sources":["../../src/http/voiceClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,sBAAsB,EAC3B,2BAA2B,EAE5B,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,MAAM,MAAM,kBAAkB,GAAG,sBAAsB,CAAC;AAExD,+EAA+E;AAC/E,eAAO,MAAM,sBAAsB;aACjC,OAAO,EAAE,EAAE;aACX,YAAY,EAAE,EAAE;aAChB,cAAc,EAAE,GAAG;aACnB,aAAa,EAAE,IAAI;IACnB,6EAA6E;aAC7E,cAAc,EAAE,KAAK;CACb,CAAC;AAEX,MAAM,WAAW,wBAAwB;IACvC,kDAAkD;IAClD,EAAE,EAAE,MAAM,CAAC;IACX,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,aAAa,GAAG,OAAO,CAAC;AAEpE,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,eAAe,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uEAAuE;IACvE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,oCAAoC;AACpC,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,kCAAkC;AAClC,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,+EAA+E;AAC/E,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,oEAAoE;AACpE,qBAAa,iBAAkB,SAAQ,2BAA2B;IAChE,SAAkB,IAAI,EAAG,mBAAmB,CAAU;CACvD;AAMD,6EAA6E;AAC7E,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,wBAAwB,GAChC,MAAM,GAAG,SAAS,CAuCpB;AAED;;;GAGG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA2B;IAErD,uFAAuF;IACvF,YAAY,OAAO,GAAE,kBAAuB,EAK3C;IAED,sEAAsE;IAChE,iBAAiB,CACrB,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,uBAAuB,CAAC,CAQlC;IAED,2CAA2C;IACrC,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE3C;IAED,gDAAgD;IAC1C,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAE7C;YAEa,IAAI;CAoBnB;AASD,gFAAgF;AAChF,wBAAgB,iBAAiB,CAC/B,OAAO,GAAE,kBAAuB,GAC/B,WAAW,CAEb"}
|