@stardeck-customer-apps/testing 0.26.0 → 0.28.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/SKILL.md +51 -1
- package/dist/index.d.mts +117 -2
- package/dist/index.d.ts +117 -2
- package/dist/index.js +569 -17
- package/dist/index.mjs +569 -17
- package/dist/next/headers-shim.js +13 -0
- package/dist/next/headers-shim.mjs +13 -0
- package/dist/setup.js +418 -16
- package/dist/setup.mjs +418 -16
- package/package.json +10 -3
package/dist/index.mjs
CHANGED
|
@@ -318,6 +318,11 @@ var state = globalSingleton("state", () => ({
|
|
|
318
318
|
cardTokens: /* @__PURE__ */ new Map(),
|
|
319
319
|
cardTokenCounter: 0,
|
|
320
320
|
cardTokenizationEnabled: true,
|
|
321
|
+
stripePaymentIntents: /* @__PURE__ */ new Map(),
|
|
322
|
+
stripePaymentIntentCounter: 0,
|
|
323
|
+
stripeIdempotentIntents: /* @__PURE__ */ new Map(),
|
|
324
|
+
stripeAllowedMethods: ["card", "promptpay"],
|
|
325
|
+
stripeChargesEnabled: true,
|
|
321
326
|
uploads: [],
|
|
322
327
|
uploadCounter: 0,
|
|
323
328
|
storageFiles: /* @__PURE__ */ new Map(),
|
|
@@ -337,6 +342,14 @@ var state = globalSingleton("state", () => ({
|
|
|
337
342
|
starlensCalls: [],
|
|
338
343
|
starlensReplies: [],
|
|
339
344
|
starlensDefaultReply: null,
|
|
345
|
+
controls: {
|
|
346
|
+
seed: {},
|
|
347
|
+
values: {},
|
|
348
|
+
unavailable: /* @__PURE__ */ new Set(),
|
|
349
|
+
lastUsage: null,
|
|
350
|
+
writes: [],
|
|
351
|
+
refusal: null
|
|
352
|
+
},
|
|
340
353
|
allowNetwork: false
|
|
341
354
|
}));
|
|
342
355
|
function requireDb() {
|
|
@@ -784,8 +797,8 @@ async function handleNeonSql(db, request) {
|
|
|
784
797
|
rowAsArray: arrayMode
|
|
785
798
|
});
|
|
786
799
|
} catch (error) {
|
|
787
|
-
const
|
|
788
|
-
return json({ message:
|
|
800
|
+
const err2 = error;
|
|
801
|
+
return json({ message: err2.message, code: err2.code }, 400);
|
|
789
802
|
}
|
|
790
803
|
}
|
|
791
804
|
|
|
@@ -2078,6 +2091,348 @@ function decodeNextRedirect(error) {
|
|
|
2078
2091
|
return new Response(null, { status, headers: { location } });
|
|
2079
2092
|
}
|
|
2080
2093
|
|
|
2094
|
+
// src/simulator/stripe-payment-intents.ts
|
|
2095
|
+
var SIM_STRIPE_PUBLISHABLE_KEY = "pk_test_sim_stripe";
|
|
2096
|
+
var SIM_STRIPE_DEFAULT_ACCOUNT = "acct_sim_default";
|
|
2097
|
+
var METHODS = ["card", "promptpay"];
|
|
2098
|
+
var RESERVED_METADATA_KEYS = ["projectId", "stardeckReferenceId", "stardeckStagingDeploymentId"];
|
|
2099
|
+
var PROMPTPAY_QR_TTL_SECONDS = 15 * 60;
|
|
2100
|
+
var PATH_RE = /^\/api\/store\/(?!beam\/)([^/]+)\/(payment-intents(?:\/([^/]+)(?:\/(cancel))?)?|elements-config|readiness)$/;
|
|
2101
|
+
function err(error, status, code) {
|
|
2102
|
+
return json({ error, code }, status);
|
|
2103
|
+
}
|
|
2104
|
+
function decode(segment) {
|
|
2105
|
+
try {
|
|
2106
|
+
return decodeURIComponent(segment);
|
|
2107
|
+
} catch {
|
|
2108
|
+
return segment;
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
function isPlayground(account) {
|
|
2112
|
+
return account === "playground";
|
|
2113
|
+
}
|
|
2114
|
+
function resolveAccount(account) {
|
|
2115
|
+
if (isPlayground(account)) return null;
|
|
2116
|
+
if (account === "default") {
|
|
2117
|
+
return process.env.STRIPE_CONNECT_ACCOUNT_ID || SIM_STRIPE_DEFAULT_ACCOUNT;
|
|
2118
|
+
}
|
|
2119
|
+
return account;
|
|
2120
|
+
}
|
|
2121
|
+
var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
2122
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2123
|
+
var APP_METADATA_MAX_KEYS = 50 - RESERVED_METADATA_KEYS.length;
|
|
2124
|
+
function isStringRecord(value) {
|
|
2125
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((v) => typeof v === "string");
|
|
2126
|
+
}
|
|
2127
|
+
function isValidMetadata(value) {
|
|
2128
|
+
if (!isStringRecord(value)) return false;
|
|
2129
|
+
const entries = Object.entries(value);
|
|
2130
|
+
return entries.length <= APP_METADATA_MAX_KEYS && entries.every(([k, v]) => k.length >= 1 && k.length <= 40 && v.length <= 500);
|
|
2131
|
+
}
|
|
2132
|
+
function isOptional(value, test) {
|
|
2133
|
+
return value === void 0 || test(value);
|
|
2134
|
+
}
|
|
2135
|
+
var isEmail = (v) => typeof v === "string" && EMAIL_RE.test(v);
|
|
2136
|
+
function validateCreate(body) {
|
|
2137
|
+
const { amount, currency, method, referenceId, idempotencyKey } = body;
|
|
2138
|
+
const checks = [
|
|
2139
|
+
[
|
|
2140
|
+
typeof amount === "number" && Number.isInteger(amount) && amount > 0,
|
|
2141
|
+
"amount must be a positive integer in minor units"
|
|
2142
|
+
],
|
|
2143
|
+
[typeof currency === "string" && currency.length === 3, "currency must be a 3-letter ISO code"],
|
|
2144
|
+
[
|
|
2145
|
+
typeof method === "string" && METHODS.includes(method),
|
|
2146
|
+
`method must be one of: ${METHODS.join(", ")}`
|
|
2147
|
+
],
|
|
2148
|
+
[
|
|
2149
|
+
typeof referenceId === "string" && referenceId.length >= 1 && referenceId.length <= 200,
|
|
2150
|
+
"referenceId must be 1 to 200 characters"
|
|
2151
|
+
],
|
|
2152
|
+
[
|
|
2153
|
+
typeof idempotencyKey === "string" && idempotencyKey.length >= 1 && idempotencyKey.length <= 200,
|
|
2154
|
+
"idempotencyKey is required (1 to 200 characters)"
|
|
2155
|
+
],
|
|
2156
|
+
[
|
|
2157
|
+
isOptional(body.description, (v) => typeof v === "string" && v.length <= 1e3),
|
|
2158
|
+
"description must be at most 1000 characters"
|
|
2159
|
+
],
|
|
2160
|
+
[isOptional(body.receiptEmail, isEmail), "receiptEmail must be an email address"],
|
|
2161
|
+
[isOptional(body.billingEmail, isEmail), "billingEmail must be an email address"],
|
|
2162
|
+
[
|
|
2163
|
+
isOptional(body.returnUrl, (v) => typeof v === "string" && URL.canParse(v)),
|
|
2164
|
+
"returnUrl must be an absolute URL"
|
|
2165
|
+
],
|
|
2166
|
+
[
|
|
2167
|
+
isOptional(body.metadata, isValidMetadata),
|
|
2168
|
+
`metadata must map keys (1-40 chars) to strings (max 500), at most ${APP_METADATA_MAX_KEYS} keys`
|
|
2169
|
+
],
|
|
2170
|
+
[
|
|
2171
|
+
typeof body.deploymentId === "string" && UUID_RE2.test(body.deploymentId),
|
|
2172
|
+
"Invalid deployment ID"
|
|
2173
|
+
]
|
|
2174
|
+
];
|
|
2175
|
+
const failed = checks.find(([ok]) => !ok);
|
|
2176
|
+
return failed ? failed[1] : null;
|
|
2177
|
+
}
|
|
2178
|
+
function appMetadata(raw) {
|
|
2179
|
+
if (!isStringRecord(raw)) return {};
|
|
2180
|
+
const out = {};
|
|
2181
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
2182
|
+
if (!RESERVED_METADATA_KEYS.includes(key)) out[key] = value;
|
|
2183
|
+
}
|
|
2184
|
+
return out;
|
|
2185
|
+
}
|
|
2186
|
+
function stableStringify(value) {
|
|
2187
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
2188
|
+
if (value && typeof value === "object") {
|
|
2189
|
+
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
2190
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
|
|
2191
|
+
}
|
|
2192
|
+
return JSON.stringify(value);
|
|
2193
|
+
}
|
|
2194
|
+
function toStripePaymentIntentSummary(intent) {
|
|
2195
|
+
const open = intent.status !== "succeeded" && intent.status !== "canceled";
|
|
2196
|
+
let actionRequired = "NONE";
|
|
2197
|
+
if (open && intent.method === "card") actionRequired = "CLIENT_CONFIRM";
|
|
2198
|
+
if (open && intent.method === "promptpay" && intent.status === "requires_action") {
|
|
2199
|
+
actionRequired = "ENCODED_IMAGE";
|
|
2200
|
+
}
|
|
2201
|
+
return {
|
|
2202
|
+
id: intent.id,
|
|
2203
|
+
status: intent.status,
|
|
2204
|
+
amount: intent.amount,
|
|
2205
|
+
currency: intent.currency,
|
|
2206
|
+
method: intent.method,
|
|
2207
|
+
referenceId: intent.referenceId,
|
|
2208
|
+
metadata: { ...intent.appMetadata },
|
|
2209
|
+
actionRequired,
|
|
2210
|
+
clientSecret: intent.method === "card" ? intent.clientSecret : null,
|
|
2211
|
+
qr: actionRequired === "ENCODED_IMAGE" ? { ...intent.qr } : null,
|
|
2212
|
+
latestChargeId: intent.latestChargeId,
|
|
2213
|
+
amountRefunded: intent.amountRefunded,
|
|
2214
|
+
lastPaymentError: intent.lastPaymentError ? { ...intent.lastPaymentError } : null,
|
|
2215
|
+
created: intent.created,
|
|
2216
|
+
livemode: false,
|
|
2217
|
+
playground: intent.playground
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
2220
|
+
function promptPayQr(id, created) {
|
|
2221
|
+
return {
|
|
2222
|
+
imageUrlPng: `https://qr.stripe.test/${id}.png`,
|
|
2223
|
+
imageUrlSvg: `https://qr.stripe.test/${id}.svg`,
|
|
2224
|
+
data: `00020101021230SIM${id}`,
|
|
2225
|
+
hostedInstructionsUrl: `https://payments.stripe.test/promptpay/${id}`,
|
|
2226
|
+
expiresAt: created + PROMPTPAY_QR_TTL_SECONDS
|
|
2227
|
+
};
|
|
2228
|
+
}
|
|
2229
|
+
function createIntent(body, account, projectId) {
|
|
2230
|
+
const problem = validateCreate(body);
|
|
2231
|
+
if (problem) return err(problem, 400, "invalid_request");
|
|
2232
|
+
const method = body.method;
|
|
2233
|
+
const currency = body.currency.toLowerCase();
|
|
2234
|
+
if (!state.stripeAllowedMethods.includes(method)) {
|
|
2235
|
+
return err(
|
|
2236
|
+
`The ${method} payment method is turned off for this account`,
|
|
2237
|
+
422,
|
|
2238
|
+
"method_not_allowed"
|
|
2239
|
+
);
|
|
2240
|
+
}
|
|
2241
|
+
if (method === "promptpay" && currency !== "thb") {
|
|
2242
|
+
return err("PromptPay only takes THB", 422, "currency_not_supported");
|
|
2243
|
+
}
|
|
2244
|
+
if (!state.stripeChargesEnabled) {
|
|
2245
|
+
return err("This Stripe account can't take payments yet", 503, "account_not_ready");
|
|
2246
|
+
}
|
|
2247
|
+
const scopedKey = JSON.stringify(["pi", projectId, account, body.idempotencyKey]);
|
|
2248
|
+
const comparable = { ...body };
|
|
2249
|
+
delete comparable.deploymentId;
|
|
2250
|
+
delete comparable.idempotencyKey;
|
|
2251
|
+
const fingerprint = stableStringify(comparable);
|
|
2252
|
+
const saved = state.stripeIdempotentIntents.get(scopedKey);
|
|
2253
|
+
if (saved) {
|
|
2254
|
+
if (saved.fingerprint !== fingerprint) {
|
|
2255
|
+
return err(
|
|
2256
|
+
"This idempotency key was already used with different parameters. Use a new key for a different payment.",
|
|
2257
|
+
409,
|
|
2258
|
+
"idempotency_conflict"
|
|
2259
|
+
);
|
|
2260
|
+
}
|
|
2261
|
+
const existing = state.stripePaymentIntents.get(saved.id);
|
|
2262
|
+
return json({ paymentIntent: toStripePaymentIntentSummary(existing) }, 201);
|
|
2263
|
+
}
|
|
2264
|
+
state.stripePaymentIntentCounter += 1;
|
|
2265
|
+
const id = `pi_sim_${state.stripePaymentIntentCounter}`;
|
|
2266
|
+
const created = Math.floor(Date.now() / 1e3);
|
|
2267
|
+
const referenceId = body.referenceId;
|
|
2268
|
+
const metadata = appMetadata(body.metadata);
|
|
2269
|
+
const intent = {
|
|
2270
|
+
id,
|
|
2271
|
+
projectId,
|
|
2272
|
+
account,
|
|
2273
|
+
connectedAccountId: resolveAccount(account),
|
|
2274
|
+
playground: isPlayground(account),
|
|
2275
|
+
status: method === "card" ? "requires_payment_method" : "requires_action",
|
|
2276
|
+
amount: body.amount,
|
|
2277
|
+
currency,
|
|
2278
|
+
method,
|
|
2279
|
+
referenceId,
|
|
2280
|
+
appMetadata: metadata,
|
|
2281
|
+
metadata: { ...metadata, projectId, stardeckReferenceId: referenceId },
|
|
2282
|
+
description: typeof body.description === "string" ? body.description : null,
|
|
2283
|
+
receiptEmail: typeof body.receiptEmail === "string" ? body.receiptEmail : null,
|
|
2284
|
+
returnUrl: typeof body.returnUrl === "string" ? body.returnUrl : null,
|
|
2285
|
+
clientSecret: `${id}_secret_sim`,
|
|
2286
|
+
qr: method === "promptpay" ? promptPayQr(id, created) : null,
|
|
2287
|
+
latestChargeId: null,
|
|
2288
|
+
amountRefunded: 0,
|
|
2289
|
+
lastPaymentError: null,
|
|
2290
|
+
created
|
|
2291
|
+
};
|
|
2292
|
+
state.stripePaymentIntents.set(id, intent);
|
|
2293
|
+
state.stripeIdempotentIntents.set(scopedKey, { fingerprint, id });
|
|
2294
|
+
console.log(
|
|
2295
|
+
`[stardeck-testing] stripe payment intent ${id} created method=${method} amount=${intent.amount} ${currency}`
|
|
2296
|
+
);
|
|
2297
|
+
return json({ paymentIntent: toStripePaymentIntentSummary(intent) }, 201);
|
|
2298
|
+
}
|
|
2299
|
+
function ownedIntent(id, projectId) {
|
|
2300
|
+
const intent = state.stripePaymentIntents.get(id);
|
|
2301
|
+
return intent && intent.projectId === projectId ? intent : void 0;
|
|
2302
|
+
}
|
|
2303
|
+
function cancelIntent(intent) {
|
|
2304
|
+
if (intent.status === "succeeded") {
|
|
2305
|
+
return err("This payment already succeeded and can't be canceled", 409, "already_succeeded");
|
|
2306
|
+
}
|
|
2307
|
+
intent.status = "canceled";
|
|
2308
|
+
return json({ paymentIntent: toStripePaymentIntentSummary(intent) });
|
|
2309
|
+
}
|
|
2310
|
+
function handleIntents(request, url, account, intentId, action, projectId) {
|
|
2311
|
+
if (!intentId && request.method === "POST") {
|
|
2312
|
+
return readJsonBody(request).then((body) => createIntent(body, account, projectId));
|
|
2313
|
+
}
|
|
2314
|
+
if (!intentId && request.method === "GET") {
|
|
2315
|
+
const referenceId = url.searchParams.get("referenceId");
|
|
2316
|
+
if (!referenceId) return err("referenceId is required", 400, "invalid_request");
|
|
2317
|
+
const paymentIntents = [...state.stripePaymentIntents.values()].filter((i) => i.projectId === projectId && i.referenceId === referenceId).map(toStripePaymentIntentSummary);
|
|
2318
|
+
return json({ paymentIntents });
|
|
2319
|
+
}
|
|
2320
|
+
if (!intentId) return null;
|
|
2321
|
+
const intent = ownedIntent(decode(intentId), projectId);
|
|
2322
|
+
if (!action && request.method === "GET") {
|
|
2323
|
+
if (!intent) return err("Payment intent not found", 404, "not_found");
|
|
2324
|
+
return json({ paymentIntent: toStripePaymentIntentSummary(intent) });
|
|
2325
|
+
}
|
|
2326
|
+
if (action === "cancel" && request.method === "POST") {
|
|
2327
|
+
return readJsonBody(request).then((body) => {
|
|
2328
|
+
if (typeof body.deploymentId !== "string" || !UUID_RE2.test(body.deploymentId)) {
|
|
2329
|
+
return err("deploymentId is required", 400, "invalid_request");
|
|
2330
|
+
}
|
|
2331
|
+
if (!intent) return err("Payment intent not found", 404, "not_found");
|
|
2332
|
+
return cancelIntent(intent);
|
|
2333
|
+
});
|
|
2334
|
+
}
|
|
2335
|
+
return null;
|
|
2336
|
+
}
|
|
2337
|
+
async function handleStripePaymentIntentRequest(request, url, projectId) {
|
|
2338
|
+
const match = url.pathname.match(PATH_RE);
|
|
2339
|
+
if (!match) return null;
|
|
2340
|
+
if (!projectId) return err("Missing authentication", 401, "unauthorized");
|
|
2341
|
+
const account = decode(match[1]);
|
|
2342
|
+
const resource = match[2];
|
|
2343
|
+
if (request.method === "GET" && !url.searchParams.get("deploymentId")) {
|
|
2344
|
+
return err("deploymentId is required", 400, "invalid_request");
|
|
2345
|
+
}
|
|
2346
|
+
if (resource === "elements-config" && request.method === "GET") {
|
|
2347
|
+
return json({
|
|
2348
|
+
publishableKey: SIM_STRIPE_PUBLISHABLE_KEY,
|
|
2349
|
+
stripeAccount: resolveAccount(account),
|
|
2350
|
+
playground: isPlayground(account)
|
|
2351
|
+
});
|
|
2352
|
+
}
|
|
2353
|
+
if (resource === "readiness" && request.method === "GET") {
|
|
2354
|
+
const methods = [...state.stripeAllowedMethods];
|
|
2355
|
+
return json({
|
|
2356
|
+
provider: "stripe",
|
|
2357
|
+
ready: state.stripeChargesEnabled && methods.length > 0,
|
|
2358
|
+
chargesEnabled: state.stripeChargesEnabled,
|
|
2359
|
+
methods,
|
|
2360
|
+
playground: isPlayground(account)
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2363
|
+
if (resource.startsWith("payment-intents")) {
|
|
2364
|
+
return handleIntents(request, url, account, match[3], match[4], projectId);
|
|
2365
|
+
}
|
|
2366
|
+
return null;
|
|
2367
|
+
}
|
|
2368
|
+
function requireIntent(id) {
|
|
2369
|
+
const intent = state.stripePaymentIntents.get(id);
|
|
2370
|
+
if (!intent) throw new Error(`[stardeck-testing] Unknown payment intent id: ${id}`);
|
|
2371
|
+
return intent;
|
|
2372
|
+
}
|
|
2373
|
+
function requireOpenIntent(id, verb) {
|
|
2374
|
+
const intent = requireIntent(id);
|
|
2375
|
+
if (intent.status === "succeeded" || intent.status === "canceled") {
|
|
2376
|
+
throw new Error(
|
|
2377
|
+
`[stardeck-testing] Can't ${verb} payment intent ${id}: it is ${intent.status}`
|
|
2378
|
+
);
|
|
2379
|
+
}
|
|
2380
|
+
return intent;
|
|
2381
|
+
}
|
|
2382
|
+
function succeedStripePaymentIntent(id) {
|
|
2383
|
+
const intent = requireOpenIntent(id, "mark paid");
|
|
2384
|
+
intent.status = "succeeded";
|
|
2385
|
+
intent.latestChargeId = `ch_sim_${id}`;
|
|
2386
|
+
intent.lastPaymentError = null;
|
|
2387
|
+
}
|
|
2388
|
+
function failStripePaymentIntent(id, code) {
|
|
2389
|
+
const intent = requireOpenIntent(id, "fail");
|
|
2390
|
+
intent.status = "requires_payment_method";
|
|
2391
|
+
intent.latestChargeId = `ch_sim_${id}_failed`;
|
|
2392
|
+
intent.lastPaymentError = {
|
|
2393
|
+
code,
|
|
2394
|
+
declineCode: code === "card_declined" ? "generic_decline" : null,
|
|
2395
|
+
message: `Simulated payment failure: ${code}`
|
|
2396
|
+
};
|
|
2397
|
+
}
|
|
2398
|
+
function cancelStripePaymentIntentById(id) {
|
|
2399
|
+
const intent = requireOpenIntent(id, "cancel");
|
|
2400
|
+
intent.status = "canceled";
|
|
2401
|
+
}
|
|
2402
|
+
function stripePaymentIntentEvent(id) {
|
|
2403
|
+
const intent = requireIntent(id);
|
|
2404
|
+
let type;
|
|
2405
|
+
if (intent.status === "succeeded") type = "payment_intent.succeeded";
|
|
2406
|
+
else if (intent.status === "canceled") type = "payment_intent.canceled";
|
|
2407
|
+
else if (intent.lastPaymentError) type = "payment_intent.payment_failed";
|
|
2408
|
+
else {
|
|
2409
|
+
throw new Error(
|
|
2410
|
+
`[stardeck-testing] Payment intent ${id} is ${intent.status}; call markPaid, failPayment or cancelPayment first`
|
|
2411
|
+
);
|
|
2412
|
+
}
|
|
2413
|
+
const error = intent.lastPaymentError;
|
|
2414
|
+
return {
|
|
2415
|
+
type,
|
|
2416
|
+
accountId: intent.connectedAccountId ?? "acct_stardeck_playground",
|
|
2417
|
+
data: {
|
|
2418
|
+
id: intent.id,
|
|
2419
|
+
object: "payment_intent",
|
|
2420
|
+
amount: intent.amount,
|
|
2421
|
+
amount_received: intent.status === "succeeded" ? intent.amount : 0,
|
|
2422
|
+
currency: intent.currency,
|
|
2423
|
+
status: intent.status,
|
|
2424
|
+
metadata: { ...intent.metadata },
|
|
2425
|
+
payment_method_types: [intent.method],
|
|
2426
|
+
latest_charge: intent.latestChargeId,
|
|
2427
|
+
last_payment_error: error ? { code: error.code, decline_code: error.declineCode, message: error.message } : null,
|
|
2428
|
+
description: intent.description,
|
|
2429
|
+
receipt_email: intent.receiptEmail,
|
|
2430
|
+
created: intent.created,
|
|
2431
|
+
livemode: false
|
|
2432
|
+
}
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2081
2436
|
// src/simulator/webhook-signing.ts
|
|
2082
2437
|
import crypto4 from "crypto";
|
|
2083
2438
|
function signEventDelivery(secret, context, rawBody) {
|
|
@@ -2111,7 +2466,7 @@ var BEAM_BOLT_PAYMENT_METHODS = [
|
|
|
2111
2466
|
"SPAY_LATER"
|
|
2112
2467
|
];
|
|
2113
2468
|
var BEAM_BOLT_INTENT_STATUSES = ["PENDING", "PAID", "FAILED", "CANCELED", "EXPIRED"];
|
|
2114
|
-
var
|
|
2469
|
+
var UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
2115
2470
|
function payErr(error, status = 400, code, details) {
|
|
2116
2471
|
const body = { error };
|
|
2117
2472
|
if (code) body.code = code;
|
|
@@ -2137,7 +2492,23 @@ function paymentOperationForRequest(request, pathname) {
|
|
|
2137
2492
|
if (request.method === "GET" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout-sessions\/[^/]+$/.test(pathname)) {
|
|
2138
2493
|
return "stripe.getCheckoutSessionStatus";
|
|
2139
2494
|
}
|
|
2140
|
-
return
|
|
2495
|
+
return stripeInlineOperation(request.method, pathname);
|
|
2496
|
+
}
|
|
2497
|
+
var STRIPE_INLINE_OPERATIONS = [
|
|
2498
|
+
["POST", /^\/api\/store\/(?!beam\/)[^/]+\/payment-intents$/, "stripe.createPaymentIntent"],
|
|
2499
|
+
["GET", /^\/api\/store\/(?!beam\/)[^/]+\/payment-intents$/, "stripe.findPaymentIntents"],
|
|
2500
|
+
["GET", /^\/api\/store\/(?!beam\/)[^/]+\/payment-intents\/[^/]+$/, "stripe.getPaymentIntent"],
|
|
2501
|
+
[
|
|
2502
|
+
"POST",
|
|
2503
|
+
/^\/api\/store\/(?!beam\/)[^/]+\/payment-intents\/[^/]+\/cancel$/,
|
|
2504
|
+
"stripe.cancelPaymentIntent"
|
|
2505
|
+
],
|
|
2506
|
+
["GET", /^\/api\/store\/(?!beam\/)[^/]+\/elements-config$/, "stripe.getElementsConfig"],
|
|
2507
|
+
["GET", /^\/api\/store\/(?!beam\/)[^/]+\/readiness$/, "stripe.getReadiness"]
|
|
2508
|
+
];
|
|
2509
|
+
function stripeInlineOperation(method, pathname) {
|
|
2510
|
+
const hit = STRIPE_INLINE_OPERATIONS.find(([m, re]) => m === method && re.test(pathname));
|
|
2511
|
+
return hit ? hit[2] : null;
|
|
2141
2512
|
}
|
|
2142
2513
|
function consumePaymentFailure(request, pathname) {
|
|
2143
2514
|
const operation = paymentOperationForRequest(request, pathname);
|
|
@@ -2167,11 +2538,11 @@ function isBoltPaymentMethod(value) {
|
|
|
2167
2538
|
function isBoltIntentStatus(value) {
|
|
2168
2539
|
return BEAM_BOLT_INTENT_STATUSES.includes(value);
|
|
2169
2540
|
}
|
|
2170
|
-
function
|
|
2171
|
-
if (Array.isArray(value)) return `[${value.map(
|
|
2541
|
+
function stableStringify2(value) {
|
|
2542
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify2).join(",")}]`;
|
|
2172
2543
|
if (value && typeof value === "object") {
|
|
2173
2544
|
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
2174
|
-
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${
|
|
2545
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify2(v)}`).join(",")}}`;
|
|
2175
2546
|
}
|
|
2176
2547
|
return JSON.stringify(value);
|
|
2177
2548
|
}
|
|
@@ -2312,10 +2683,16 @@ async function deliverEvent(envelope, handler, options) {
|
|
|
2312
2683
|
body: envelope
|
|
2313
2684
|
});
|
|
2314
2685
|
}
|
|
2315
|
-
async function handlePaymentsRequest(request, url) {
|
|
2686
|
+
async function handlePaymentsRequest(request, url, signedProjectId = null) {
|
|
2316
2687
|
const pathname = url.pathname;
|
|
2317
2688
|
const injectedFailure = consumePaymentFailure(request, pathname);
|
|
2318
2689
|
if (injectedFailure) return injectedFailure;
|
|
2690
|
+
const inlineStripeResponse = await handleStripePaymentIntentRequest(
|
|
2691
|
+
request,
|
|
2692
|
+
url,
|
|
2693
|
+
signedProjectId
|
|
2694
|
+
);
|
|
2695
|
+
if (inlineStripeResponse) return inlineStripeResponse;
|
|
2319
2696
|
const directChargeResponse = await handleDirectChargeRequest(request, pathname);
|
|
2320
2697
|
if (directChargeResponse) return directChargeResponse;
|
|
2321
2698
|
if (/\/billing-portal$/.test(pathname)) {
|
|
@@ -2414,7 +2791,7 @@ async function handlePaymentsRequest(request, url) {
|
|
|
2414
2791
|
code: "too_small"
|
|
2415
2792
|
});
|
|
2416
2793
|
}
|
|
2417
|
-
if (typeof body.deploymentId !== "string" || !
|
|
2794
|
+
if (typeof body.deploymentId !== "string" || !UUID_RE3.test(body.deploymentId)) {
|
|
2418
2795
|
issues.push({
|
|
2419
2796
|
path: ["deploymentId"],
|
|
2420
2797
|
message: "Invalid uuid",
|
|
@@ -2598,7 +2975,7 @@ async function handlePaymentsRequest(request, url) {
|
|
|
2598
2975
|
const comparable = { ...body };
|
|
2599
2976
|
delete comparable.deploymentId;
|
|
2600
2977
|
delete comparable.idempotencyKey;
|
|
2601
|
-
const fingerprint =
|
|
2978
|
+
const fingerprint = stableStringify2(comparable);
|
|
2602
2979
|
if (scopedKey) {
|
|
2603
2980
|
const saved = state.idempotentCheckouts.get(scopedKey);
|
|
2604
2981
|
if (saved) {
|
|
@@ -2673,6 +3050,10 @@ function createPayments() {
|
|
|
2673
3050
|
state.paymentFailures.push({ operation, failure: failure2 });
|
|
2674
3051
|
},
|
|
2675
3052
|
markPaid(id) {
|
|
3053
|
+
if (state.stripePaymentIntents.has(id)) {
|
|
3054
|
+
succeedStripePaymentIntent(id);
|
|
3055
|
+
return;
|
|
3056
|
+
}
|
|
2676
3057
|
const session = state.sessionStatuses.get(id);
|
|
2677
3058
|
if (session) {
|
|
2678
3059
|
session.status = "complete";
|
|
@@ -2700,7 +3081,26 @@ function createPayments() {
|
|
|
2700
3081
|
}
|
|
2701
3082
|
link.status = status;
|
|
2702
3083
|
},
|
|
2703
|
-
|
|
3084
|
+
failPayment(paymentIntentId, code = "card_declined") {
|
|
3085
|
+
failStripePaymentIntent(paymentIntentId, code);
|
|
3086
|
+
},
|
|
3087
|
+
cancelPayment(paymentIntentId) {
|
|
3088
|
+
cancelStripePaymentIntentById(paymentIntentId);
|
|
3089
|
+
},
|
|
3090
|
+
get stripePaymentIntents() {
|
|
3091
|
+
return [...state.stripePaymentIntents.values()];
|
|
3092
|
+
},
|
|
3093
|
+
getStripePaymentIntent(paymentIntentId) {
|
|
3094
|
+
return state.stripePaymentIntents.get(paymentIntentId);
|
|
3095
|
+
},
|
|
3096
|
+
setStripePaymentMethods(methods) {
|
|
3097
|
+
state.stripeAllowedMethods = [...methods];
|
|
3098
|
+
},
|
|
3099
|
+
setStripeChargesEnabled(enabled) {
|
|
3100
|
+
state.stripeChargesEnabled = enabled;
|
|
3101
|
+
},
|
|
3102
|
+
async deliverStripeEvent(handler, input, options) {
|
|
3103
|
+
const event = "paymentIntentId" in input ? stripePaymentIntentEvent(input.paymentIntentId) : input;
|
|
2704
3104
|
const envelope = {
|
|
2705
3105
|
id: `evt_test_${crypto5.randomUUID()}`,
|
|
2706
3106
|
kind: "stripe_webhook",
|
|
@@ -2816,6 +3216,11 @@ function createPayments() {
|
|
|
2816
3216
|
state.cardTokens.clear();
|
|
2817
3217
|
state.cardTokenCounter = 0;
|
|
2818
3218
|
state.cardTokenizationEnabled = true;
|
|
3219
|
+
state.stripePaymentIntents.clear();
|
|
3220
|
+
state.stripePaymentIntentCounter = 0;
|
|
3221
|
+
state.stripeIdempotentIntents.clear();
|
|
3222
|
+
state.stripeAllowedMethods = ["card", "promptpay"];
|
|
3223
|
+
state.stripeChargesEnabled = true;
|
|
2819
3224
|
},
|
|
2820
3225
|
get count() {
|
|
2821
3226
|
return state.checkouts.length;
|
|
@@ -3588,6 +3993,141 @@ function createEdge() {
|
|
|
3588
3993
|
};
|
|
3589
3994
|
}
|
|
3590
3995
|
|
|
3996
|
+
// src/simulator/controls.ts
|
|
3997
|
+
var CONTROLS_STATE_SYMBOL = /* @__PURE__ */ Symbol.for("stardeck.controls.state");
|
|
3998
|
+
var USER_TOKEN_HEADER = "x-stardeck-user-token";
|
|
3999
|
+
var DEFAULTS = {
|
|
4000
|
+
"flag.paymentsSetupStep": false,
|
|
4001
|
+
"plan.locations": { included: 3, max: null, extraMonthly: { amount: 990, currency: "THB" } },
|
|
4002
|
+
"plan.multiEntity": true,
|
|
4003
|
+
"payments.provider": "beam",
|
|
4004
|
+
"payments.methods": { card: true, promptPay: true }
|
|
4005
|
+
};
|
|
4006
|
+
var REFUSAL_STATUS = {
|
|
4007
|
+
NOT_WRITABLE: 400,
|
|
4008
|
+
INVALID_VALUE: 400,
|
|
4009
|
+
UNAUTHENTICATED: 401,
|
|
4010
|
+
FORBIDDEN: 403,
|
|
4011
|
+
WRITE_NOT_ALLOWED_HERE: 403,
|
|
4012
|
+
PROVIDER_NOT_READY: 409,
|
|
4013
|
+
UNAVAILABLE: 503
|
|
4014
|
+
};
|
|
4015
|
+
var controlsModule = null;
|
|
4016
|
+
function loadControls() {
|
|
4017
|
+
controlsModule ??= import("@stardeck-customer-apps/controls");
|
|
4018
|
+
return controlsModule;
|
|
4019
|
+
}
|
|
4020
|
+
function resetControlsClientCache() {
|
|
4021
|
+
const clientState = globalThis[CONTROLS_STATE_SYMBOL];
|
|
4022
|
+
clientState?.reset?.();
|
|
4023
|
+
}
|
|
4024
|
+
function initControls(seed) {
|
|
4025
|
+
state.controls.seed = { ...seed };
|
|
4026
|
+
restoreControls();
|
|
4027
|
+
}
|
|
4028
|
+
function restoreControls() {
|
|
4029
|
+
state.controls.values = { ...DEFAULTS, ...state.controls.seed };
|
|
4030
|
+
state.controls.unavailable = /* @__PURE__ */ new Set();
|
|
4031
|
+
state.controls.lastUsage = null;
|
|
4032
|
+
state.controls.writes = [];
|
|
4033
|
+
state.controls.refusal = null;
|
|
4034
|
+
resetControlsClientCache();
|
|
4035
|
+
}
|
|
4036
|
+
function clearControls() {
|
|
4037
|
+
restoreControls();
|
|
4038
|
+
}
|
|
4039
|
+
function currentControlsBody() {
|
|
4040
|
+
const controls = {};
|
|
4041
|
+
for (const [key, value] of Object.entries(state.controls.values)) {
|
|
4042
|
+
controls[key] = state.controls.unavailable.has(key) ? { ok: false, reason: "unavailable (simulated)" } : { ok: true, value };
|
|
4043
|
+
}
|
|
4044
|
+
return controls;
|
|
4045
|
+
}
|
|
4046
|
+
function refusalResponse(refusal) {
|
|
4047
|
+
return json({ error: refusal }, REFUSAL_STATUS[refusal.code]);
|
|
4048
|
+
}
|
|
4049
|
+
function handleSync(body) {
|
|
4050
|
+
const usage = body.usage;
|
|
4051
|
+
if (typeof usage === "object" && usage !== null) {
|
|
4052
|
+
const locations = usage.locations;
|
|
4053
|
+
state.controls.lastUsage = typeof locations === "number" && Number.isInteger(locations) && locations >= 0 ? { locations } : {};
|
|
4054
|
+
}
|
|
4055
|
+
return json({ controls: currentControlsBody() });
|
|
4056
|
+
}
|
|
4057
|
+
async function handleWrite(request, key) {
|
|
4058
|
+
const { CONTROLS, isWritableControl } = await loadControls();
|
|
4059
|
+
if (!isWritableControl(key)) {
|
|
4060
|
+
return refusalResponse({
|
|
4061
|
+
code: "NOT_WRITABLE",
|
|
4062
|
+
message: `${key} cannot be changed from the app`
|
|
4063
|
+
});
|
|
4064
|
+
}
|
|
4065
|
+
const userToken = request.headers.get(USER_TOKEN_HEADER);
|
|
4066
|
+
if (!userToken) {
|
|
4067
|
+
return refusalResponse({
|
|
4068
|
+
code: "UNAUTHENTICATED",
|
|
4069
|
+
message: "A signed-in person is required (X-Stardeck-User-Token)"
|
|
4070
|
+
});
|
|
4071
|
+
}
|
|
4072
|
+
const body = await readJsonBody(request);
|
|
4073
|
+
const parsed = CONTROLS[key].schema.safeParse(body.value);
|
|
4074
|
+
if (!parsed.success) {
|
|
4075
|
+
return refusalResponse({
|
|
4076
|
+
code: "INVALID_VALUE",
|
|
4077
|
+
message: parsed.error.issues[0]?.message ?? "Invalid value"
|
|
4078
|
+
});
|
|
4079
|
+
}
|
|
4080
|
+
const refusal = state.controls.refusal;
|
|
4081
|
+
if (refusal) {
|
|
4082
|
+
state.controls.refusal = null;
|
|
4083
|
+
return refusalResponse(refusal);
|
|
4084
|
+
}
|
|
4085
|
+
state.controls.values[key] = parsed.data;
|
|
4086
|
+
state.controls.unavailable.delete(key);
|
|
4087
|
+
state.controls.writes.push({ key, value: parsed.data, userToken });
|
|
4088
|
+
return json({ controls: currentControlsBody() });
|
|
4089
|
+
}
|
|
4090
|
+
var CONTROLS_PATH = /^\/api\/deployments\/[^/]+\/controls(?:\/([^/]+))?$/;
|
|
4091
|
+
async function handleControlsRequest(request, url) {
|
|
4092
|
+
const match = url.pathname.match(CONTROLS_PATH);
|
|
4093
|
+
const key = match?.[1] ? decodeURIComponent(match[1]) : null;
|
|
4094
|
+
if (!key && request.method === "POST") {
|
|
4095
|
+
return handleSync(await readJsonBody(request));
|
|
4096
|
+
}
|
|
4097
|
+
if (key && request.method === "PUT") {
|
|
4098
|
+
return handleWrite(request, key);
|
|
4099
|
+
}
|
|
4100
|
+
return json(
|
|
4101
|
+
{
|
|
4102
|
+
error: { code: "UNAVAILABLE", message: `No simulator for ${request.method} ${url.pathname}` }
|
|
4103
|
+
},
|
|
4104
|
+
404
|
|
4105
|
+
);
|
|
4106
|
+
}
|
|
4107
|
+
function createControls() {
|
|
4108
|
+
return {
|
|
4109
|
+
set(key, value) {
|
|
4110
|
+
state.controls.values[key] = value;
|
|
4111
|
+
state.controls.unavailable.delete(key);
|
|
4112
|
+
resetControlsClientCache();
|
|
4113
|
+
},
|
|
4114
|
+
setUnavailable(key) {
|
|
4115
|
+
state.controls.unavailable.add(key);
|
|
4116
|
+
resetControlsClientCache();
|
|
4117
|
+
},
|
|
4118
|
+
lastUsage() {
|
|
4119
|
+
return state.controls.lastUsage;
|
|
4120
|
+
},
|
|
4121
|
+
writes() {
|
|
4122
|
+
return state.controls.writes;
|
|
4123
|
+
},
|
|
4124
|
+
refuseNextWrite(refusal) {
|
|
4125
|
+
state.controls.refusal = refusal;
|
|
4126
|
+
},
|
|
4127
|
+
clear: restoreControls
|
|
4128
|
+
};
|
|
4129
|
+
}
|
|
4130
|
+
|
|
3591
4131
|
// src/simulator/schedules.ts
|
|
3592
4132
|
async function handleScheduleWake(request, scheduleKey) {
|
|
3593
4133
|
if (request.method !== "POST") return failure(`Method ${request.method} not allowed`, 405);
|
|
@@ -3770,7 +4310,7 @@ function requiresDeploymentHmac(request, url) {
|
|
|
3770
4310
|
return !isPresignedPut;
|
|
3771
4311
|
}
|
|
3772
4312
|
const dataStoreMatch = url.pathname.match(/^\/api\/data-stores\/[^/]+(\/.*)?$/);
|
|
3773
|
-
const
|
|
4313
|
+
const isEmail2 = url.pathname === "/api/email/send";
|
|
3774
4314
|
const identitiesMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/identities(\/.*)?$/);
|
|
3775
4315
|
const storeMatch = url.pathname.match(/^\/api\/store\//);
|
|
3776
4316
|
const messagingMatch = url.pathname.match(
|
|
@@ -3779,8 +4319,10 @@ function requiresDeploymentHmac(request, url) {
|
|
|
3779
4319
|
const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
|
|
3780
4320
|
const isStarlens = url.pathname === "/api/starlens/extract";
|
|
3781
4321
|
const scheduleWakeMatch = url.pathname.match(SCHEDULE_WAKE_PATH);
|
|
3782
|
-
|
|
4322
|
+
const controlsMatch = url.pathname.match(CONTROLS_PATH2);
|
|
4323
|
+
return !!(controlsMatch || isStarlens || scheduleWakeMatch || dataStoreMatch || isEmail2 || identitiesMatch || storeMatch || messagingMatch || edgeMatch);
|
|
3783
4324
|
}
|
|
4325
|
+
var CONTROLS_PATH2 = /^\/api\/deployments\/[^/]+\/controls(\/[^/]+)?$/;
|
|
3784
4326
|
var SCHEDULE_WAKE_PATH = /^\/api\/deployments\/[^/]+\/schedules\/([^/]+)\/wake$/;
|
|
3785
4327
|
function deploymentIdFromPath(url) {
|
|
3786
4328
|
const match = url.pathname.match(/\/api\/deployments\/([^/]+)(?:\/|$)/);
|
|
@@ -3802,7 +4344,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
3802
4344
|
return authMatch[1].endsWith("verify") ? handleAuthVerify(request) : handleAuthRefresh(request);
|
|
3803
4345
|
}
|
|
3804
4346
|
const dataStoreMatch = url.pathname.match(/^\/api\/data-stores\/[^/]+(\/.*)?$/);
|
|
3805
|
-
const
|
|
4347
|
+
const isEmail2 = url.pathname === "/api/email/send";
|
|
3806
4348
|
const identitiesMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/identities(\/.*)?$/);
|
|
3807
4349
|
const storeMatch = url.pathname.match(/^\/api\/store\//);
|
|
3808
4350
|
const messagingMatch = url.pathname.match(
|
|
@@ -3811,6 +4353,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
3811
4353
|
const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
|
|
3812
4354
|
const isStorageHost = url.hostname === STORAGE_TEST_HOST;
|
|
3813
4355
|
let signedDeploymentId = null;
|
|
4356
|
+
let signedProjectId = null;
|
|
3814
4357
|
if (requiresDeploymentHmac(request, url)) {
|
|
3815
4358
|
const authHeader = request.headers.get("X-Stardeck-Auth");
|
|
3816
4359
|
if (!authHeader) {
|
|
@@ -3823,6 +4366,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
3823
4366
|
return failure("Invalid authentication", 401);
|
|
3824
4367
|
}
|
|
3825
4368
|
signedDeploymentId = authPayload.deploymentId;
|
|
4369
|
+
signedProjectId = authPayload.projectId;
|
|
3826
4370
|
}
|
|
3827
4371
|
if (isStorageHost) {
|
|
3828
4372
|
return handleStorageRequest(request, url);
|
|
@@ -3830,14 +4374,14 @@ async function handleSimulatedRequest(request, url) {
|
|
|
3830
4374
|
if (url.hostname === SIM_BEAM_HOST) {
|
|
3831
4375
|
return handleCardTokenRequest(request, url);
|
|
3832
4376
|
}
|
|
3833
|
-
if (
|
|
4377
|
+
if (isEmail2 && request.method === "POST") {
|
|
3834
4378
|
return handleEmailSend(request);
|
|
3835
4379
|
}
|
|
3836
4380
|
if (identitiesMatch) {
|
|
3837
4381
|
return handleIdentitiesRequest(request, identitiesMatch[1] ?? "");
|
|
3838
4382
|
}
|
|
3839
4383
|
if (storeMatch) {
|
|
3840
|
-
return handlePaymentsRequest(request, url);
|
|
4384
|
+
return handlePaymentsRequest(request, url, signedProjectId);
|
|
3841
4385
|
}
|
|
3842
4386
|
if (messagingMatch) {
|
|
3843
4387
|
const channel = messagingMatch[1];
|
|
@@ -3846,6 +4390,9 @@ async function handleSimulatedRequest(request, url) {
|
|
|
3846
4390
|
if (edgeMatch) {
|
|
3847
4391
|
return handleEdgeRequest(request, edgeMatch[1] ?? "");
|
|
3848
4392
|
}
|
|
4393
|
+
if (CONTROLS_PATH2.test(url.pathname)) {
|
|
4394
|
+
return handleControlsRequest(request, url);
|
|
4395
|
+
}
|
|
3849
4396
|
const scheduleWakeMatch = url.pathname.match(SCHEDULE_WAKE_PATH);
|
|
3850
4397
|
if (scheduleWakeMatch) {
|
|
3851
4398
|
return handleScheduleWake(request, decodeURIComponent(scheduleWakeMatch[1]));
|
|
@@ -3874,7 +4421,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
3874
4421
|
}
|
|
3875
4422
|
}
|
|
3876
4423
|
return failure(
|
|
3877
|
-
`[stardeck-testing] No simulator for ${request.method} ${url.pathname}. Supported: data-store query/mutate/schema, email send, identities CRUD, payments store/checkout, storage upload/files, messaging send, edge print/display/bindings, starlens extract, schedule wakes, auth verify/refresh, Neon /sql.`,
|
|
4424
|
+
`[stardeck-testing] No simulator for ${request.method} ${url.pathname}. Supported: data-store query/mutate/schema, email send, identities CRUD, payments store/checkout, storage upload/files, messaging send, edge print/display/bindings, app controls sync/write, starlens extract, schedule wakes, auth verify/refresh, Neon /sql.`,
|
|
3878
4425
|
404
|
|
3879
4426
|
);
|
|
3880
4427
|
}
|
|
@@ -4023,6 +4570,7 @@ async function createTestApp(options = {}) {
|
|
|
4023
4570
|
const initialLiffId = options.liffId ?? null;
|
|
4024
4571
|
state.liffId = initialLiffId;
|
|
4025
4572
|
installFetchRouter();
|
|
4573
|
+
initControls(options.controls);
|
|
4026
4574
|
const schemaSql = loadSchemaSql(options) ?? "";
|
|
4027
4575
|
const db = await bootWithSchema(schemaSql);
|
|
4028
4576
|
state.db = db;
|
|
@@ -4054,6 +4602,7 @@ async function createTestApp(options = {}) {
|
|
|
4054
4602
|
const edge = createEdge();
|
|
4055
4603
|
const starlens = createStarlens();
|
|
4056
4604
|
const schedules = createSchedules();
|
|
4605
|
+
const controls = createControls();
|
|
4057
4606
|
const app = {
|
|
4058
4607
|
db,
|
|
4059
4608
|
inbox,
|
|
@@ -4064,6 +4613,7 @@ async function createTestApp(options = {}) {
|
|
|
4064
4613
|
edge,
|
|
4065
4614
|
starlens,
|
|
4066
4615
|
schedules,
|
|
4616
|
+
controls,
|
|
4067
4617
|
async query(sql, params = []) {
|
|
4068
4618
|
const result = await db.query(sql, params);
|
|
4069
4619
|
return result.rows;
|
|
@@ -4113,6 +4663,7 @@ async function createTestApp(options = {}) {
|
|
|
4113
4663
|
edge.clear();
|
|
4114
4664
|
starlens.clear();
|
|
4115
4665
|
schedules.clear();
|
|
4666
|
+
clearControls();
|
|
4116
4667
|
},
|
|
4117
4668
|
async close() {
|
|
4118
4669
|
state.db = null;
|
|
@@ -4129,6 +4680,7 @@ async function createTestApp(options = {}) {
|
|
|
4129
4680
|
edge.clear();
|
|
4130
4681
|
starlens.clear();
|
|
4131
4682
|
schedules.clear();
|
|
4683
|
+
clearControls();
|
|
4132
4684
|
uninstallFetchRouter();
|
|
4133
4685
|
await db.close();
|
|
4134
4686
|
}
|