@doany-ai/sdk 0.2.5 → 0.2.7
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 +101 -1
- package/dist/client.types.d.ts +12 -0
- package/dist/index.d.ts +1 -1
- package/dist/modules/connectors.d.ts +7 -0
- package/dist/modules/connectors.js +71 -8
- package/dist/modules/connectors.types.d.ts +85 -0
- package/dist/modules/payments.types.d.ts +25 -0
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createEntitiesModule } from "./modules/entities.js";
|
|
|
3
3
|
import { createIntegrationsModule } from "./modules/integrations.js";
|
|
4
4
|
import { createAuthModule } from "./modules/auth.js";
|
|
5
5
|
import { createSsoModule } from "./modules/sso.js";
|
|
6
|
-
import { createConnectorsModule, createUserConnectorsModule, } from "./modules/connectors.js";
|
|
6
|
+
import { confirmAppUserConnection, createConnectorsModule, createUserConnectorsModule, } from "./modules/connectors.js";
|
|
7
7
|
import { getAccessToken } from "./utils/auth-utils.js";
|
|
8
8
|
import { createFunctionsModule } from "./modules/functions.js";
|
|
9
9
|
import { createPaymentsModule } from "./modules/payments.js";
|
|
@@ -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 = {};
|
|
@@ -213,6 +231,14 @@ export function createClient(config) {
|
|
|
213
231
|
},
|
|
214
232
|
};
|
|
215
233
|
// If authentication is required, verify token and redirect to login if needed
|
|
234
|
+
// An app user coming back from a connector sign-in carries a one-time code
|
|
235
|
+
// (doany_connector, doany_connector_code). Confirm it with THIS user's
|
|
236
|
+
// session and drop it from the URL — app code never has to do this, and a
|
|
237
|
+
// forwarded link cannot attach a stranger's account because the confirm
|
|
238
|
+
// rides the session, not the code alone.
|
|
239
|
+
if (typeof window !== "undefined" && window.location) {
|
|
240
|
+
void finishPendingConnectorSignIn(axiosClient, appId);
|
|
241
|
+
}
|
|
216
242
|
if (requiresAuth && typeof window !== "undefined") {
|
|
217
243
|
// We perform this check asynchronously to not block client creation
|
|
218
244
|
setTimeout(async () => {
|
|
@@ -357,6 +383,8 @@ export function createClientFromRequest(request) {
|
|
|
357
383
|
const functionsVersion = request.headers.get("Doany-Functions-Version");
|
|
358
384
|
const stateHeader = request.headers.get("Doany-State");
|
|
359
385
|
const dataEnvHeader = request.headers.get("X-Data-Env");
|
|
386
|
+
const originHeader = request.headers.get("X-Doany-Origin");
|
|
387
|
+
const runIdHeader = request.headers.get("X-Run-Id");
|
|
360
388
|
if (!appId) {
|
|
361
389
|
throw new Error("Doany-App-Id header is required, but is was not found on the request");
|
|
362
390
|
}
|
|
@@ -394,6 +422,41 @@ export function createClientFromRequest(request) {
|
|
|
394
422
|
if (dataEnvHeader === "dev" || dataEnvHeader === "prod") {
|
|
395
423
|
additionalHeaders["X-Data-Env"] = dataEnvHeader;
|
|
396
424
|
}
|
|
425
|
+
// The browser Origin of the request that reached the backend, forwarded to us
|
|
426
|
+
// by the function proxy. Payments resolves test-vs-live from the address the
|
|
427
|
+
// site is served from, and a function's own call has no Origin at all — so
|
|
428
|
+
// without this every `getCheckoutSession` from inside a fulfilment function
|
|
429
|
+
// fails with "Origin header is required for payments", which is the shape the
|
|
430
|
+
// payments guide documents.
|
|
431
|
+
//
|
|
432
|
+
// Reduced to an origin rather than shape-matched. The proxy falls back to
|
|
433
|
+
// Referer when a request carries no Origin — a same-origin GET or HEAD, which
|
|
434
|
+
// is exactly what `functions.fetch` sends — and a Referer carries the path:
|
|
435
|
+
// `https://shop.example/thanks?session_id=…`. Matching "origin-shaped" threw
|
|
436
|
+
// every one of those away and left the function right back where it started.
|
|
437
|
+
//
|
|
438
|
+
// Parsed, not trusted: the value only ever becomes a LOOKUP against this
|
|
439
|
+
// app's own hostnames, and the backend honours it solely for a service-role
|
|
440
|
+
// caller. `URL` throws on anything that is not a URL, which is the check.
|
|
441
|
+
if (originHeader) {
|
|
442
|
+
try {
|
|
443
|
+
const { origin } = new URL(originHeader);
|
|
444
|
+
if (origin !== "null") {
|
|
445
|
+
additionalHeaders["X-Doany-Origin"] = origin;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
catch (_a) {
|
|
449
|
+
// Not a URL. Forwarding it would only make the failure harder to read.
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
// The agent run this work belongs to. A backend function called from a
|
|
453
|
+
// run and one an app calls on its own are the same request shape, and this
|
|
454
|
+
// header is the only thing that tells them apart — the platform bills the
|
|
455
|
+
// run for the first and the app for the second. Dropped here, an agent's
|
|
456
|
+
// connector calls were attributed to the app.
|
|
457
|
+
if (runIdHeader) {
|
|
458
|
+
additionalHeaders["X-Run-Id"] = runIdHeader.slice(0, 128);
|
|
459
|
+
}
|
|
397
460
|
return createClient({
|
|
398
461
|
serverUrl: serverUrlHeader || "https://api.doany.ai",
|
|
399
462
|
appId,
|
|
@@ -403,3 +466,40 @@ export function createClientFromRequest(request) {
|
|
|
403
466
|
headers: additionalHeaders,
|
|
404
467
|
});
|
|
405
468
|
}
|
|
469
|
+
const CONNECTOR_RETURN_PARAMS = ["doany_connector", "doany_connector_code", "doany_connector_status"];
|
|
470
|
+
async function finishPendingConnectorSignIn(axios, appId) {
|
|
471
|
+
var _a, _b;
|
|
472
|
+
let params;
|
|
473
|
+
try {
|
|
474
|
+
params = new URLSearchParams(window.location.search);
|
|
475
|
+
}
|
|
476
|
+
catch (_c) {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
const connectorId = params.get("doany_connector");
|
|
480
|
+
const code = params.get("doany_connector_code");
|
|
481
|
+
if (!connectorId || !code)
|
|
482
|
+
return;
|
|
483
|
+
try {
|
|
484
|
+
await confirmAppUserConnection(axios, appId, connectorId, code);
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
console.error("Could not finish the connector sign-in:", error);
|
|
488
|
+
// The code is one-time, so it stays in the URL while the failure may
|
|
489
|
+
// pass — no network, a 5xx, or a sign-in the app has not restored yet
|
|
490
|
+
// (401): a reload, or the app's own login, retries the confirmation.
|
|
491
|
+
// A definite rejection (spent, wrong or expired code) is cleared like a
|
|
492
|
+
// success, because retrying cannot help.
|
|
493
|
+
// The client's interceptor turns Axios failures into DoanyError, which
|
|
494
|
+
// carries the HTTP code as `status`; a raw Axios error keeps it under
|
|
495
|
+
// `response.status`.
|
|
496
|
+
const status = (_a = error === null || error === void 0 ? void 0 : error.status) !== null && _a !== void 0 ? _a : (_b = error === null || error === void 0 ? void 0 : error.response) === null || _b === void 0 ? void 0 : _b.status;
|
|
497
|
+
const definite = typeof status === "number" && status >= 400 && status < 500 && ![401, 408, 429].includes(status);
|
|
498
|
+
if (!definite)
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
for (const name of CONNECTOR_RETURN_PARAMS)
|
|
502
|
+
params.delete(name);
|
|
503
|
+
const query = params.toString();
|
|
504
|
+
window.history.replaceState({}, document.title, `${window.location.pathname}${query ? `?${query}` : ""}${window.location.hash}`);
|
|
505
|
+
}
|
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
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,6 @@ export type { AiGatewayModule, AiGatewayConnection, } from "./modules/ai-gateway
|
|
|
13
13
|
export type { AppLogsModule } from "./modules/app-logs.types.js";
|
|
14
14
|
export type { PaymentsModule, CheckoutLineItem, CreateCheckoutParams, CreateCheckoutResult, CreateEmbeddedCheckoutResult, CheckoutSession, SubscriptionState, BillingPortalParams, } from "./modules/payments.types.js";
|
|
15
15
|
export type { SsoModule, SsoAccessTokenResponse } from "./modules/sso.types.js";
|
|
16
|
-
export type { ConnectorsModule, UserConnectorsModule, } from "./modules/connectors.types.js";
|
|
16
|
+
export type { ConnectorsModule, UserConnectorsModule, ConnectorApiRequest, ConnectorApiResponse, ConnectorProxyRawResponse, } from "./modules/connectors.types.js";
|
|
17
17
|
export type { CustomIntegrationsModule, CustomIntegrationCallParams, CustomIntegrationCallResponse, } from "./modules/custom-integrations.types.js";
|
|
18
18
|
export type { GetAccessTokenOptions, SaveAccessTokenOptions, RemoveAccessTokenOptions, GetLoginUrlOptions, } from "./utils/auth-utils.types.js";
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { AxiosInstance } from "axios";
|
|
2
2
|
import { ConnectorsModule, UserConnectorsModule } from "./connectors.types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Finishes an app-user connection with the one-time code the platform put in
|
|
5
|
+
* the return URL. Called by the client on page load, never by app code: the
|
|
6
|
+
* confirm must ride the same user's session that started the sign-in.
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export declare function confirmAppUserConnection(axios: AxiosInstance, appId: string, connectorId: string, code: string): Promise<void>;
|
|
3
10
|
/**
|
|
4
11
|
* Creates the Connectors module for the Doany SDK.
|
|
5
12
|
*
|
|
@@ -1,3 +1,60 @@
|
|
|
1
|
+
const CONNECTOR_API_METHODS = new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]);
|
|
2
|
+
function assertNonEmptyString(value, label) {
|
|
3
|
+
if (!value || typeof value !== "string") {
|
|
4
|
+
throw new Error(`${label} is required and must be a string`);
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* One proxied call, exactly as @base44/sdk does it: validate the request,
|
|
9
|
+
* post the raw shape, map the raw (snake_case) answer to the typed one.
|
|
10
|
+
*/
|
|
11
|
+
async function proxyCall(axios, url, request) {
|
|
12
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
13
|
+
if (!request || typeof request !== "object") {
|
|
14
|
+
throw new Error("Request is required and must be an object");
|
|
15
|
+
}
|
|
16
|
+
assertNonEmptyString(request.path, "Request path");
|
|
17
|
+
const method = (_a = request.method) !== null && _a !== void 0 ? _a : "GET";
|
|
18
|
+
if (!CONNECTOR_API_METHODS.has(method)) {
|
|
19
|
+
throw new Error("Request method must be one of GET, POST, PUT, PATCH, DELETE, or HEAD");
|
|
20
|
+
}
|
|
21
|
+
const response = await axios.post(url, {
|
|
22
|
+
method,
|
|
23
|
+
// Omitted when unset so the proxy applies the connector's default host.
|
|
24
|
+
...(request.host == null ? {} : { host: request.host }),
|
|
25
|
+
path: request.path,
|
|
26
|
+
query: (_b = request.query) !== null && _b !== void 0 ? _b : {},
|
|
27
|
+
headers: (_c = request.headers) !== null && _c !== void 0 ? _c : {},
|
|
28
|
+
body: (_d = request.body) !== null && _d !== void 0 ? _d : null,
|
|
29
|
+
});
|
|
30
|
+
const data = response;
|
|
31
|
+
return {
|
|
32
|
+
success: data.success,
|
|
33
|
+
phase: data.phase,
|
|
34
|
+
status: (_e = data.status_code) !== null && _e !== void 0 ? _e : null,
|
|
35
|
+
data: data.data,
|
|
36
|
+
dataBase64: (_f = data.data_base64) !== null && _f !== void 0 ? _f : null,
|
|
37
|
+
contentType: (_g = data.content_type) !== null && _g !== void 0 ? _g : null,
|
|
38
|
+
headers: (_h = data.headers) !== null && _h !== void 0 ? _h : {},
|
|
39
|
+
creditsCharged: (_j = data.credits_charged) !== null && _j !== void 0 ? _j : 0,
|
|
40
|
+
// A 200 envelope can still carry a failure (`sent_unconfirmed`, or the
|
|
41
|
+
// provider's own error); without these the caller knows a write may
|
|
42
|
+
// have happened but not why the answer is missing.
|
|
43
|
+
...(data.error == null ? {} : { error: data.error }),
|
|
44
|
+
...(data.error_message == null ? {} : { errorMessage: data.error_message }),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Finishes an app-user connection with the one-time code the platform put in
|
|
49
|
+
* the return URL. Called by the client on page load, never by app code: the
|
|
50
|
+
* confirm must ride the same user's session that started the sign-in.
|
|
51
|
+
* @internal
|
|
52
|
+
*/
|
|
53
|
+
export async function confirmAppUserConnection(axios, appId, connectorId, code) {
|
|
54
|
+
assertNonEmptyString(connectorId, "Connector ID");
|
|
55
|
+
assertNonEmptyString(code, "Confirmation code");
|
|
56
|
+
await axios.post(`/apps/${appId}/app-user-auth/connectors/${encodeURIComponent(connectorId)}/confirm`, { code });
|
|
57
|
+
}
|
|
1
58
|
/**
|
|
2
59
|
* Creates the Connectors module for the Doany SDK.
|
|
3
60
|
*
|
|
@@ -8,6 +65,16 @@
|
|
|
8
65
|
*/
|
|
9
66
|
export function createConnectorsModule(axios, appId) {
|
|
10
67
|
return {
|
|
68
|
+
async callApi(integrationType, request) {
|
|
69
|
+
assertNonEmptyString(integrationType, "Integration type");
|
|
70
|
+
// Encoded so a runtime-built identifier can only ever select a
|
|
71
|
+
// connector, never re-target another route under this token.
|
|
72
|
+
return proxyCall(axios, `/apps/${appId}/connectors/${encodeURIComponent(integrationType)}/call`, request);
|
|
73
|
+
},
|
|
74
|
+
async callCurrentAppUserApi(integrationType, request) {
|
|
75
|
+
assertNonEmptyString(integrationType, "Integration type");
|
|
76
|
+
return proxyCall(axios, `/apps/${appId}/app-user-connectors/${encodeURIComponent(integrationType)}/call`, request);
|
|
77
|
+
},
|
|
11
78
|
/**
|
|
12
79
|
* Retrieve an OAuth access token for a specific external integration type.
|
|
13
80
|
* @deprecated Use getConnection(integrationType) and use the returned accessToken (and connectionConfig when needed) instead.
|
|
@@ -81,18 +148,14 @@ export function createConnectorsModule(axios, appId) {
|
|
|
81
148
|
export function createUserConnectorsModule(axios, appId) {
|
|
82
149
|
return {
|
|
83
150
|
async connectAppUser(connectorId) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
const response = await axios.post(`/apps/${appId}/app-user-auth/connectors/${connectorId}/initiate`);
|
|
151
|
+
assertNonEmptyString(connectorId, "Connector ID");
|
|
152
|
+
const response = await axios.post(`/apps/${appId}/app-user-auth/connectors/${encodeURIComponent(connectorId)}/initiate`);
|
|
88
153
|
const data = response;
|
|
89
154
|
return data.redirect_url;
|
|
90
155
|
},
|
|
91
156
|
async disconnectAppUser(connectorId) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
await axios.delete(`/apps/${appId}/app-user-auth/connectors/${connectorId}`);
|
|
157
|
+
assertNonEmptyString(connectorId, "Connector ID");
|
|
158
|
+
await axios.delete(`/apps/${appId}/app-user-auth/connectors/${encodeURIComponent(connectorId)}`);
|
|
96
159
|
},
|
|
97
160
|
};
|
|
98
161
|
}
|
|
@@ -41,6 +41,65 @@ export interface AppUserConnectorConnectionResponse {
|
|
|
41
41
|
/** Key-value configuration for the connection, or `null` if the connector does not provide one. */
|
|
42
42
|
connectionConfig: Record<string, string> | null;
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* A request proxied to the external service on the app's behalf. The
|
|
46
|
+
* platform injects the credential; the app never sees a token.
|
|
47
|
+
*/
|
|
48
|
+
export interface ConnectorApiRequest {
|
|
49
|
+
/** HTTP method. Defaults to `GET`. */
|
|
50
|
+
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD";
|
|
51
|
+
/** Path relative to the provider's API root. Must start with `/`. */
|
|
52
|
+
path: string;
|
|
53
|
+
/** Provider host to call; defaults to the connector's primary API host. Must be one of the connector's allowed hosts. */
|
|
54
|
+
host?: string;
|
|
55
|
+
/** Query parameters. */
|
|
56
|
+
query?: Record<string, string>;
|
|
57
|
+
/** Extra request headers. `Authorization` is ignored — the platform sets it. */
|
|
58
|
+
headers?: Record<string, string>;
|
|
59
|
+
/** JSON body. */
|
|
60
|
+
body?: unknown;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* What a proxied call produced. HTTP 200 from the platform means only that
|
|
64
|
+
* the proxy worked; read `success`, `phase` and `status` for what the
|
|
65
|
+
* provider did.
|
|
66
|
+
*/
|
|
67
|
+
export interface ConnectorApiResponse<T = unknown> {
|
|
68
|
+
/** The provider answered 2xx. */
|
|
69
|
+
success: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* `not_sent` — the request never left the platform (safe to retry).
|
|
72
|
+
* `responded` — the provider answered; see `status`.
|
|
73
|
+
* `sent_unconfirmed` — sent, but no answer came back; a write may have happened. Never auto-retry a write on this.
|
|
74
|
+
*/
|
|
75
|
+
phase: "not_sent" | "responded" | "sent_unconfirmed";
|
|
76
|
+
/** The provider's HTTP status, or `null` when there was no response. */
|
|
77
|
+
status: number | null;
|
|
78
|
+
/** The provider's JSON body, when it sent one. */
|
|
79
|
+
data: T | null;
|
|
80
|
+
/** The provider's body as base64 when it was binary. */
|
|
81
|
+
dataBase64: string | null;
|
|
82
|
+
contentType: string | null;
|
|
83
|
+
headers: Record<string, string>;
|
|
84
|
+
/** Credits charged for this call. */
|
|
85
|
+
creditsCharged: number;
|
|
86
|
+
/** Set when `phase` is not `responded`. */
|
|
87
|
+
error?: string;
|
|
88
|
+
errorMessage?: string;
|
|
89
|
+
}
|
|
90
|
+
/** The proxy's raw answer on the wire (snake_case); {@link ConnectorApiResponse} is its typed form. */
|
|
91
|
+
export interface ConnectorProxyRawResponse<T = unknown> {
|
|
92
|
+
success: boolean;
|
|
93
|
+
phase: "not_sent" | "responded" | "sent_unconfirmed";
|
|
94
|
+
status_code?: number | null;
|
|
95
|
+
data: T | null;
|
|
96
|
+
data_base64?: string | null;
|
|
97
|
+
content_type?: string | null;
|
|
98
|
+
headers?: Record<string, string>;
|
|
99
|
+
credits_charged?: number;
|
|
100
|
+
error?: string;
|
|
101
|
+
error_message?: string;
|
|
102
|
+
}
|
|
44
103
|
/**
|
|
45
104
|
* Connectors module for managing OAuth tokens for external services.
|
|
46
105
|
*
|
|
@@ -131,6 +190,32 @@ export interface AppUserConnectorConnectionResponse {
|
|
|
131
190
|
* If you're working in a TypeScript project, you can generate types from your app's connector configurations to get autocomplete on integration type names when calling {@link getConnection}. See the [Dynamic Types](/developers/references/sdk/getting-started/dynamic-types) guide to get started.
|
|
132
191
|
*/
|
|
133
192
|
export interface ConnectorsModule {
|
|
193
|
+
/**
|
|
194
|
+
* Calls the external service through the platform with the app's shared connection.
|
|
195
|
+
*
|
|
196
|
+
* This is the only way a backend function reaches a connector on doany: the platform injects the credential and forwards the request. The token methods below answer 403.
|
|
197
|
+
*
|
|
198
|
+
* @param integrationType - The connector's integration type, e.g. `'gmail'`.
|
|
199
|
+
* @param request - Method, path (relative to the provider's API root), query, headers and body.
|
|
200
|
+
* @returns The provider's answer wrapped in `success` / `phase` / `status`.
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```typescript
|
|
204
|
+
* const res = await doany.asServiceRole.connectors.callApi('gmail', {
|
|
205
|
+
* method: 'POST',
|
|
206
|
+
* path: '/gmail/v1/users/me/messages/send',
|
|
207
|
+
* body: { raw },
|
|
208
|
+
* });
|
|
209
|
+
* if (!res.success) { /* res.status is the provider's code, res.data its body *\/ }
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
callApi<T = unknown>(integrationType: ConnectorIntegrationType, request: ConnectorApiRequest): Promise<ConnectorApiResponse<T>>;
|
|
213
|
+
/**
|
|
214
|
+
* Calls the external service as the signed-in app user, through their own connection.
|
|
215
|
+
*
|
|
216
|
+
* Only works from a backend function the user triggered (the platform needs their identity). Same envelope as {@linkcode callApi}.
|
|
217
|
+
*/
|
|
218
|
+
callCurrentAppUserApi<T = unknown>(integrationType: ConnectorIntegrationType, request: ConnectorApiRequest): Promise<ConnectorApiResponse<T>>;
|
|
134
219
|
/**
|
|
135
220
|
* Retrieves an OAuth access token for a specific [external integration type](#available-connectors).
|
|
136
221
|
*
|
|
@@ -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
|
/**
|