@doany-ai/sdk 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.js +46 -0
- package/dist/client.types.d.ts +12 -0
- package/dist/modules/payments.types.d.ts +25 -0
- package/dist/utils/axios-client.js +12 -0
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -186,6 +186,24 @@ export function createClient(config) {
|
|
|
186
186
|
integrations: createIntegrationsModule(serviceRoleAxiosClient, appId),
|
|
187
187
|
sso: createSsoModule(serviceRoleAxiosClient, appId),
|
|
188
188
|
connectors: createConnectorsModule(serviceRoleAxiosClient, appId),
|
|
189
|
+
// Reading a checkout or a plan with server credentials, which is what a
|
|
190
|
+
// fulfilment function is doing and what the platform requires of it:
|
|
191
|
+
// payments resolves test-vs-live from the address the site is served
|
|
192
|
+
// from, and the browser Origin a function carries is honoured only for a
|
|
193
|
+
// service-role caller. Through the ordinary client that check cannot pass,
|
|
194
|
+
// so the call fails no matter what the function forwards.
|
|
195
|
+
//
|
|
196
|
+
// The two reads only, and enforced here rather than by the type alone:
|
|
197
|
+
// opening a checkout is a decision that belongs to the browser that is
|
|
198
|
+
// actually there, and a function naming its own mode could charge a real
|
|
199
|
+
// card from a preview.
|
|
200
|
+
payments: (() => {
|
|
201
|
+
const full = createPaymentsModule(serviceRoleAxiosClient, appId);
|
|
202
|
+
return {
|
|
203
|
+
getCheckoutSession: full.getCheckoutSession.bind(full),
|
|
204
|
+
getSubscription: full.getSubscription.bind(full),
|
|
205
|
+
};
|
|
206
|
+
})(),
|
|
189
207
|
functions: createFunctionsModule(serviceRoleFunctionsAxiosClient, appId, {
|
|
190
208
|
getAuthHeaders: () => {
|
|
191
209
|
const headers = {};
|
|
@@ -357,6 +375,7 @@ export function createClientFromRequest(request) {
|
|
|
357
375
|
const functionsVersion = request.headers.get("Doany-Functions-Version");
|
|
358
376
|
const stateHeader = request.headers.get("Doany-State");
|
|
359
377
|
const dataEnvHeader = request.headers.get("X-Data-Env");
|
|
378
|
+
const originHeader = request.headers.get("X-Doany-Origin");
|
|
360
379
|
if (!appId) {
|
|
361
380
|
throw new Error("Doany-App-Id header is required, but is was not found on the request");
|
|
362
381
|
}
|
|
@@ -394,6 +413,33 @@ export function createClientFromRequest(request) {
|
|
|
394
413
|
if (dataEnvHeader === "dev" || dataEnvHeader === "prod") {
|
|
395
414
|
additionalHeaders["X-Data-Env"] = dataEnvHeader;
|
|
396
415
|
}
|
|
416
|
+
// The browser Origin of the request that reached the backend, forwarded to us
|
|
417
|
+
// by the function proxy. Payments resolves test-vs-live from the address the
|
|
418
|
+
// site is served from, and a function's own call has no Origin at all — so
|
|
419
|
+
// without this every `getCheckoutSession` from inside a fulfilment function
|
|
420
|
+
// fails with "Origin header is required for payments", which is the shape the
|
|
421
|
+
// payments guide documents.
|
|
422
|
+
//
|
|
423
|
+
// Reduced to an origin rather than shape-matched. The proxy falls back to
|
|
424
|
+
// Referer when a request carries no Origin — a same-origin GET or HEAD, which
|
|
425
|
+
// is exactly what `functions.fetch` sends — and a Referer carries the path:
|
|
426
|
+
// `https://shop.example/thanks?session_id=…`. Matching "origin-shaped" threw
|
|
427
|
+
// every one of those away and left the function right back where it started.
|
|
428
|
+
//
|
|
429
|
+
// Parsed, not trusted: the value only ever becomes a LOOKUP against this
|
|
430
|
+
// app's own hostnames, and the backend honours it solely for a service-role
|
|
431
|
+
// caller. `URL` throws on anything that is not a URL, which is the check.
|
|
432
|
+
if (originHeader) {
|
|
433
|
+
try {
|
|
434
|
+
const { origin } = new URL(originHeader);
|
|
435
|
+
if (origin !== "null") {
|
|
436
|
+
additionalHeaders["X-Doany-Origin"] = origin;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
catch (_a) {
|
|
440
|
+
// Not a URL. Forwarding it would only make the failure harder to read.
|
|
441
|
+
}
|
|
442
|
+
}
|
|
397
443
|
return createClient({
|
|
398
444
|
serverUrl: serverUrlHeader || "https://api.doany.ai",
|
|
399
445
|
appId,
|
package/dist/client.types.d.ts
CHANGED
|
@@ -142,6 +142,18 @@ export interface DoanyClient {
|
|
|
142
142
|
functions: FunctionsModule;
|
|
143
143
|
/** {@link IntegrationsModule | Integrations module} with elevated permissions. */
|
|
144
144
|
integrations: IntegrationsModule;
|
|
145
|
+
/**
|
|
146
|
+
* Reading a checkout session or a subscription with server credentials.
|
|
147
|
+
*
|
|
148
|
+
* A fulfilment function must use THIS one, not `doany.payments`: the
|
|
149
|
+
* platform honours a function's forwarded browser Origin only for a
|
|
150
|
+
* service-role caller, so the ordinary client cannot resolve a mode and
|
|
151
|
+
* the read fails whatever the function forwards.
|
|
152
|
+
*
|
|
153
|
+
* Only the reads are here. There is no service-role checkout — opening one
|
|
154
|
+
* is a decision that belongs to the browser that is actually there.
|
|
155
|
+
*/
|
|
156
|
+
payments: Pick<PaymentsModule, "getCheckoutSession" | "getSubscription">;
|
|
145
157
|
/** {@link SsoModule | SSO module} for generating SSO tokens.
|
|
146
158
|
* @internal
|
|
147
159
|
*/
|
|
@@ -113,6 +113,19 @@ export type SubscriptionState = {
|
|
|
113
113
|
interval: string | null;
|
|
114
114
|
amount: number | null;
|
|
115
115
|
currency: string | null;
|
|
116
|
+
/**
|
|
117
|
+
* WHICH plan they are on — the `Product` record id, not what it costs.
|
|
118
|
+
*
|
|
119
|
+
* A plan switch happens on Stripe's billing portal and never returns through
|
|
120
|
+
* the success page, so this is the only way an app learns the new tier.
|
|
121
|
+
*
|
|
122
|
+
* Do not stand `amount` in for it: prices change, and two tiers can charge
|
|
123
|
+
* the same in different currencies or billing periods, so an app gating
|
|
124
|
+
* features on a number grants the wrong ones.
|
|
125
|
+
*
|
|
126
|
+
* `null` for a subscription older than durable prices.
|
|
127
|
+
*/
|
|
128
|
+
product_id: string | null;
|
|
116
129
|
metadata: Record<string, string>;
|
|
117
130
|
};
|
|
118
131
|
export type BillingPortalParams = {
|
|
@@ -158,6 +171,18 @@ export type CheckoutSession = {
|
|
|
158
171
|
name: string | null;
|
|
159
172
|
quantity: number | null;
|
|
160
173
|
amount_total: number | null;
|
|
174
|
+
/**
|
|
175
|
+
* The `Product` record this line was sold from — **gate on this, never on
|
|
176
|
+
* `name`**.
|
|
177
|
+
*
|
|
178
|
+
* A product's name is founder-editable and changes without warning, so an
|
|
179
|
+
* app granting access by comparing names stops matching the moment one is
|
|
180
|
+
* renamed. This id is stable for the life of the record.
|
|
181
|
+
*
|
|
182
|
+
* `null` for an order placed before durable prices, or one whose Stripe
|
|
183
|
+
* product id had to be hashed for length.
|
|
184
|
+
*/
|
|
185
|
+
product_id: string | null;
|
|
161
186
|
}>;
|
|
162
187
|
metadata: Record<string, string>;
|
|
163
188
|
/**
|
|
@@ -123,6 +123,18 @@ export function createAxiosClient({ baseURL, headers = {}, token, interceptRespo
|
|
|
123
123
|
Accept: "application/json",
|
|
124
124
|
...headers,
|
|
125
125
|
},
|
|
126
|
+
// Where there is no XHR — a backend function on Cloudflare Workers, Deno,
|
|
127
|
+
// Node — axios falls back to its fetch adapter, which builds a `Request`
|
|
128
|
+
// and hands it to `fetch`. A `Request` defaults to `cache: "default"`, and
|
|
129
|
+
// workerd accepts only "no-store", so it throws `Unsupported cache mode:
|
|
130
|
+
// default` before anything reaches the network. Measured on a live app:
|
|
131
|
+
// every `asServiceRole.entities.*` call from inside a backend function
|
|
132
|
+
// failed this way, which is exactly the fulfilment path a payment is told
|
|
133
|
+
// to use — an anonymous checkout took the money and wrote no order.
|
|
134
|
+
//
|
|
135
|
+
// Browsers keep the XHR adapter and ignore this entirely. Where it does
|
|
136
|
+
// apply, not caching is the correct behaviour for an API call anyway.
|
|
137
|
+
fetchOptions: { cache: "no-store" },
|
|
126
138
|
});
|
|
127
139
|
// Add token to requests if available
|
|
128
140
|
if (token) {
|