@stardeck-customer-apps/testing 0.20.0 → 0.22.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 +29 -5
- package/dist/index.d.mts +43 -1
- package/dist/index.d.ts +43 -1
- package/dist/index.js +411 -9
- package/dist/index.mjs +411 -9
- package/dist/next/headers-shim.js +5 -0
- package/dist/next/headers-shim.mjs +5 -0
- package/dist/setup.js +360 -9
- package/dist/setup.mjs +360 -9
- package/package.json +2 -2
package/dist/setup.js
CHANGED
|
@@ -76,6 +76,11 @@ var state = globalSingleton("state", () => ({
|
|
|
76
76
|
boltIntentCounter: 0,
|
|
77
77
|
boltCharges: [],
|
|
78
78
|
boltChargeCounter: 0,
|
|
79
|
+
directCharges: /* @__PURE__ */ new Map(),
|
|
80
|
+
directChargeCounter: 0,
|
|
81
|
+
cardTokens: /* @__PURE__ */ new Map(),
|
|
82
|
+
cardTokenCounter: 0,
|
|
83
|
+
cardTokenizationEnabled: true,
|
|
79
84
|
uploads: [],
|
|
80
85
|
uploadCounter: 0,
|
|
81
86
|
storageFiles: /* @__PURE__ */ new Map(),
|
|
@@ -1225,6 +1230,297 @@ async function handleIdentitiesRequest(request, subPath) {
|
|
|
1225
1230
|
return failure(`No identities simulator for ${method} .../identities${subPath}`, 404);
|
|
1226
1231
|
}
|
|
1227
1232
|
|
|
1233
|
+
// src/simulator/beam-direct-charges.ts
|
|
1234
|
+
var SIM_BEAM_HOST = "beam.stardeck.test";
|
|
1235
|
+
var SIM_BEAM_MERCHANT_ID = "sim_merchant";
|
|
1236
|
+
var SIM_BEAM_PUBLISHABLE_KEY = "pk_test_sim";
|
|
1237
|
+
var DIRECT_CHARGE_METHODS = /* @__PURE__ */ new Set([
|
|
1238
|
+
"CARD",
|
|
1239
|
+
"CARD_INSTALLMENTS",
|
|
1240
|
+
"CARD_TOKEN",
|
|
1241
|
+
"CARD_TOKEN_INSTALLMENTS",
|
|
1242
|
+
"QR_PROMPT_PAY",
|
|
1243
|
+
"ALIPAY",
|
|
1244
|
+
"ALIPAY_PLUS",
|
|
1245
|
+
"WECHAT_PAY",
|
|
1246
|
+
"TRUE_MONEY",
|
|
1247
|
+
"LINE_PAY",
|
|
1248
|
+
"SHOPEE_PAY",
|
|
1249
|
+
"KPLUS",
|
|
1250
|
+
"MAKE",
|
|
1251
|
+
"SCB_EASY",
|
|
1252
|
+
"KRUNGSRI_APP",
|
|
1253
|
+
"BANGKOK_BANK_APP",
|
|
1254
|
+
"SPAY_LATER"
|
|
1255
|
+
]);
|
|
1256
|
+
var TEST_CARDS = {
|
|
1257
|
+
"4111111111111111": { outcome: "SUCCEEDED" },
|
|
1258
|
+
"5372074248113841": { outcome: "SUCCEEDED" },
|
|
1259
|
+
"378282246310005": { outcome: "SUCCEEDED" },
|
|
1260
|
+
"4953261730509988": { outcome: "REDIRECT" },
|
|
1261
|
+
"4389783280144506": { outcome: "REDIRECT" },
|
|
1262
|
+
"4943129900084541": { outcome: "FAILED", failureCode: "CH_INSUFFICIENT_FUNDS" },
|
|
1263
|
+
"4111111111000025": { outcome: "FAILED", failureCode: "CH_CARD_DECLINED" },
|
|
1264
|
+
"4111111111000033": { outcome: "FAILED", failureCode: "CH_CARD_RESTRICTED" }
|
|
1265
|
+
};
|
|
1266
|
+
var SIM_QR_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";
|
|
1267
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
1268
|
+
function invalid(issues) {
|
|
1269
|
+
return json({ error: "Invalid request body", details: issues }, 400);
|
|
1270
|
+
}
|
|
1271
|
+
var DEVICE_TYPES = /* @__PURE__ */ new Set(["ANDROID", "IOS", "WEB"]);
|
|
1272
|
+
function issue(path, message, code = "custom") {
|
|
1273
|
+
return { path, message, code };
|
|
1274
|
+
}
|
|
1275
|
+
function validateChargeFields(body) {
|
|
1276
|
+
const issues = [];
|
|
1277
|
+
const isInt = (v) => typeof v === "number" && Number.isInteger(v);
|
|
1278
|
+
if (!isInt(body.amount) || body.amount < 1) {
|
|
1279
|
+
issues.push(issue(["amount"], "Number must be greater than 0", "too_small"));
|
|
1280
|
+
}
|
|
1281
|
+
if (typeof body.deploymentId !== "string" || !UUID_RE.test(body.deploymentId)) {
|
|
1282
|
+
issues.push(issue(["deploymentId"], "Invalid deployment ID", "invalid_string"));
|
|
1283
|
+
}
|
|
1284
|
+
if (body.referenceId !== void 0 && (typeof body.referenceId !== "string" || body.referenceId.length > 100)) {
|
|
1285
|
+
issues.push(issue(["referenceId"], "Too long", "too_big"));
|
|
1286
|
+
}
|
|
1287
|
+
const expiry = body.expiryDurationInSec;
|
|
1288
|
+
if (expiry !== void 0 && (!isInt(expiry) || expiry < 60 || expiry > 3600)) {
|
|
1289
|
+
issues.push(issue(["expiryDurationInSec"], "Must be an integer from 60 to 3600", "too_big"));
|
|
1290
|
+
}
|
|
1291
|
+
if (body.returnUrl !== void 0 && (typeof body.returnUrl !== "string" || !URL.canParse(body.returnUrl))) {
|
|
1292
|
+
issues.push(issue(["returnUrl"], "Invalid URL", "invalid_format"));
|
|
1293
|
+
}
|
|
1294
|
+
if (body.deviceType !== void 0 && !DEVICE_TYPES.has(String(body.deviceType))) {
|
|
1295
|
+
issues.push(issue(["deviceType"], "Invalid enum value", "invalid_value"));
|
|
1296
|
+
}
|
|
1297
|
+
const key = body.idempotencyKey;
|
|
1298
|
+
if (key !== void 0 && (typeof key !== "string" || key.length < 1 || key.length > 255)) {
|
|
1299
|
+
issues.push(issue(["idempotencyKey"], "Must be 1 to 255 characters", "too_small"));
|
|
1300
|
+
}
|
|
1301
|
+
return issues;
|
|
1302
|
+
}
|
|
1303
|
+
function validateChargeMethod(body) {
|
|
1304
|
+
const method = body.paymentMethod;
|
|
1305
|
+
const type = method?.paymentMethodType;
|
|
1306
|
+
if (typeof type !== "string" || !DIRECT_CHARGE_METHODS.has(type)) {
|
|
1307
|
+
return [issue(["paymentMethod", "paymentMethodType"], "Invalid enum value", "invalid_value")];
|
|
1308
|
+
}
|
|
1309
|
+
const issues = [];
|
|
1310
|
+
const isToken = type === "CARD_TOKEN" || type === "CARD_TOKEN_INSTALLMENTS";
|
|
1311
|
+
const token = method?.cardToken;
|
|
1312
|
+
if (isToken && (typeof token?.cardTokenId !== "string" || token.cardTokenId === "")) {
|
|
1313
|
+
issues.push(
|
|
1314
|
+
issue(["paymentMethod", "cardToken"], `${type} requires paymentMethod.cardToken.cardTokenId`)
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
if (token?.securityCode !== void 0 && !/^\d{3,4}$/.test(String(token.securityCode))) {
|
|
1318
|
+
issues.push(
|
|
1319
|
+
issue(
|
|
1320
|
+
["paymentMethod", "cardToken", "securityCode"],
|
|
1321
|
+
"Invalid security code",
|
|
1322
|
+
"invalid_format"
|
|
1323
|
+
)
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
if (type === "CARD_TOKEN_INSTALLMENTS" && ![3, 4, 6, 10].includes(method?.installmentPeriod)) {
|
|
1327
|
+
issues.push(
|
|
1328
|
+
issue(
|
|
1329
|
+
["paymentMethod", "installmentPeriod"],
|
|
1330
|
+
"CARD_TOKEN_INSTALLMENTS requires paymentMethod.installmentPeriod (3, 4, 6 or 10)"
|
|
1331
|
+
)
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
if (type !== "QR_PROMPT_PAY" && !body.returnUrl) {
|
|
1335
|
+
issues.push(
|
|
1336
|
+
issue(
|
|
1337
|
+
["returnUrl"],
|
|
1338
|
+
`${type} sends the shopper away to pay; returnUrl is required to bring them back`
|
|
1339
|
+
)
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
1342
|
+
if (type === "CARD" || type === "CARD_INSTALLMENTS") {
|
|
1343
|
+
issues.push(
|
|
1344
|
+
issue(
|
|
1345
|
+
["paymentMethod", "paymentMethodType"],
|
|
1346
|
+
`${type} takes raw card data; tokenize the card in the browser and charge CARD_TOKEN instead`
|
|
1347
|
+
)
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
return issues;
|
|
1351
|
+
}
|
|
1352
|
+
function validateDirectCharge(body) {
|
|
1353
|
+
return [...validateChargeFields(body), ...validateChargeMethod(body)];
|
|
1354
|
+
}
|
|
1355
|
+
function chargeResponse(charge) {
|
|
1356
|
+
const response = {
|
|
1357
|
+
chargeId: charge.id,
|
|
1358
|
+
actionRequired: charge.actionRequired,
|
|
1359
|
+
paymentMethodType: charge.paymentMethodType
|
|
1360
|
+
};
|
|
1361
|
+
if (charge.actionRequired === "ENCODED_IMAGE") {
|
|
1362
|
+
response.encodedImage = {
|
|
1363
|
+
imageBase64Encoded: SIM_QR_PNG,
|
|
1364
|
+
rawData: `00020101021230SIM${charge.id}`,
|
|
1365
|
+
expiry: charge.expiresAt
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
if (charge.actionRequired === "REDIRECT") {
|
|
1369
|
+
response.redirect = { redirectUrl: charge.redirectUrl };
|
|
1370
|
+
response.redirectUrl = charge.redirectUrl;
|
|
1371
|
+
}
|
|
1372
|
+
return response;
|
|
1373
|
+
}
|
|
1374
|
+
function nextChargeId() {
|
|
1375
|
+
state.directChargeCounter += 1;
|
|
1376
|
+
return `ch_sim_${state.directChargeCounter}`;
|
|
1377
|
+
}
|
|
1378
|
+
function createCharge(body) {
|
|
1379
|
+
const method = body.paymentMethod;
|
|
1380
|
+
const type = method.paymentMethodType;
|
|
1381
|
+
const id = nextChargeId();
|
|
1382
|
+
const now3 = /* @__PURE__ */ new Date();
|
|
1383
|
+
const expirySec = typeof body.expiryDurationInSec === "number" ? body.expiryDurationInSec : 30 * 60;
|
|
1384
|
+
const returnUrl = typeof body.returnUrl === "string" ? body.returnUrl : null;
|
|
1385
|
+
let actionRequired = "REDIRECT";
|
|
1386
|
+
let status = "PENDING";
|
|
1387
|
+
let failureCode = null;
|
|
1388
|
+
let cardLast4 = null;
|
|
1389
|
+
if (type === "QR_PROMPT_PAY") {
|
|
1390
|
+
actionRequired = "ENCODED_IMAGE";
|
|
1391
|
+
} else if (type === "CARD_TOKEN" || type === "CARD_TOKEN_INSTALLMENTS") {
|
|
1392
|
+
const token = state.cardTokens.get(method.cardToken.cardTokenId);
|
|
1393
|
+
if (!token) {
|
|
1394
|
+
return json({ error: "Card token not found", code: "CARD_TOKEN_NOT_FOUND" }, 404);
|
|
1395
|
+
}
|
|
1396
|
+
cardLast4 = token.last4;
|
|
1397
|
+
const card = TEST_CARDS[token.pan] ?? {
|
|
1398
|
+
outcome: "FAILED",
|
|
1399
|
+
failureCode: "CH_PROCESSING_FAILED"
|
|
1400
|
+
};
|
|
1401
|
+
if (card.outcome === "REDIRECT") {
|
|
1402
|
+
actionRequired = "REDIRECT";
|
|
1403
|
+
} else {
|
|
1404
|
+
actionRequired = "NONE";
|
|
1405
|
+
status = card.outcome;
|
|
1406
|
+
failureCode = card.failureCode ?? null;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
const charge = {
|
|
1410
|
+
id,
|
|
1411
|
+
status,
|
|
1412
|
+
amount: body.amount,
|
|
1413
|
+
currency: String(body.currency ?? "THB"),
|
|
1414
|
+
paymentMethodType: type,
|
|
1415
|
+
referenceId: typeof body.referenceId === "string" ? body.referenceId : null,
|
|
1416
|
+
deviceType: typeof body.deviceType === "string" ? body.deviceType : null,
|
|
1417
|
+
returnUrl,
|
|
1418
|
+
actionRequired,
|
|
1419
|
+
redirectUrl: actionRequired === "REDIRECT" ? `https://${SIM_BEAM_HOST}/pay/${id}${returnUrl ? `?returnUrl=${encodeURIComponent(returnUrl)}` : ""}` : null,
|
|
1420
|
+
expiresAt: new Date(now3.getTime() + expirySec * 1e3).toISOString(),
|
|
1421
|
+
failureCode,
|
|
1422
|
+
cardLast4,
|
|
1423
|
+
idempotencyKey: typeof body.idempotencyKey === "string" ? body.idempotencyKey : null,
|
|
1424
|
+
createdAt: now3.toISOString()
|
|
1425
|
+
};
|
|
1426
|
+
state.directCharges.set(id, charge);
|
|
1427
|
+
return charge;
|
|
1428
|
+
}
|
|
1429
|
+
async function handleDirectChargeRequest(request, pathname) {
|
|
1430
|
+
if (request.method === "POST" && /^\/api\/store\/beam\/[^/]+\/charges$/.test(pathname)) {
|
|
1431
|
+
const body = await readJsonBody(request);
|
|
1432
|
+
const issues = validateDirectCharge(body);
|
|
1433
|
+
if (issues.length > 0) return invalid(issues);
|
|
1434
|
+
const key = typeof body.idempotencyKey === "string" ? body.idempotencyKey : null;
|
|
1435
|
+
if (key) {
|
|
1436
|
+
const existing = [...state.directCharges.values()].find((c) => c.idempotencyKey === key);
|
|
1437
|
+
if (existing) return json(chargeResponse(existing));
|
|
1438
|
+
}
|
|
1439
|
+
const created = createCharge(body);
|
|
1440
|
+
if (created instanceof Response) return created;
|
|
1441
|
+
return json(chargeResponse(created));
|
|
1442
|
+
}
|
|
1443
|
+
const getMatch = pathname.match(/^\/api\/store\/beam\/[^/]+\/charges\/([^/]+)$/);
|
|
1444
|
+
if (request.method === "GET" && getMatch) {
|
|
1445
|
+
const charge = state.directCharges.get(decodeURIComponent(getMatch[1]));
|
|
1446
|
+
if (!charge) return json({ error: "Charge not found", code: "NOT_FOUND" }, 404);
|
|
1447
|
+
return json({ charge: toChargeDetail(charge) });
|
|
1448
|
+
}
|
|
1449
|
+
if (request.method === "GET" && /^\/api\/store\/beam\/[^/]+\/card-tokenization$/.test(pathname)) {
|
|
1450
|
+
if (!state.cardTokenizationEnabled) {
|
|
1451
|
+
return json(
|
|
1452
|
+
{
|
|
1453
|
+
error: "Add the Beam publishable key to this merchant account to take cards in your own checkout",
|
|
1454
|
+
code: "CARD_TOKENIZATION_NOT_CONFIGURED"
|
|
1455
|
+
},
|
|
1456
|
+
409
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
return json({
|
|
1460
|
+
merchantId: SIM_BEAM_MERCHANT_ID,
|
|
1461
|
+
publishableKey: SIM_BEAM_PUBLISHABLE_KEY,
|
|
1462
|
+
tokenizeUrl: `https://${SIM_BEAM_HOST}/client/v1/card-tokens`,
|
|
1463
|
+
isSandbox: true
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
return null;
|
|
1467
|
+
}
|
|
1468
|
+
function toChargeDetail(charge) {
|
|
1469
|
+
return {
|
|
1470
|
+
id: charge.id,
|
|
1471
|
+
chargeId: charge.id,
|
|
1472
|
+
status: charge.status,
|
|
1473
|
+
amount: charge.amount,
|
|
1474
|
+
currency: charge.currency,
|
|
1475
|
+
paymentMethodType: charge.paymentMethodType,
|
|
1476
|
+
...charge.referenceId !== null ? { referenceId: charge.referenceId } : {},
|
|
1477
|
+
...charge.failureCode ? { failureCode: charge.failureCode } : {},
|
|
1478
|
+
source: "API",
|
|
1479
|
+
createdAt: charge.createdAt
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
async function handleCardTokenRequest(request, url) {
|
|
1483
|
+
if (request.method !== "POST" || url.pathname !== "/client/v1/card-tokens") {
|
|
1484
|
+
return json({ error: `No Beam simulator for ${request.method} ${url.pathname}` }, 404);
|
|
1485
|
+
}
|
|
1486
|
+
const expected = `Basic ${btoa(`${SIM_BEAM_MERCHANT_ID}:${SIM_BEAM_PUBLISHABLE_KEY}`)}`;
|
|
1487
|
+
if (request.headers.get("Authorization") !== expected) {
|
|
1488
|
+
return json(
|
|
1489
|
+
{
|
|
1490
|
+
message: "invalid credentials",
|
|
1491
|
+
error: { errorCode: "INVALID_CREDENTIALS_ERROR", errorMessage: "invalid credentials" }
|
|
1492
|
+
},
|
|
1493
|
+
401
|
|
1494
|
+
);
|
|
1495
|
+
}
|
|
1496
|
+
const body = await readJsonBody(request);
|
|
1497
|
+
const pan = typeof body.pan === "string" ? body.pan : "";
|
|
1498
|
+
const month = body.expiryMonth;
|
|
1499
|
+
const year = body.expiryYear;
|
|
1500
|
+
if (!/^\d{12,19}$/.test(pan) || typeof month !== "number" || month < 1 || month > 12 || typeof year !== "number" || year < 0 || year > 99) {
|
|
1501
|
+
return json(
|
|
1502
|
+
{
|
|
1503
|
+
message: "inputs are failing validation",
|
|
1504
|
+
error: {
|
|
1505
|
+
errorCode: "API_VALIDATION_ERROR",
|
|
1506
|
+
errorMessage: "inputs are failing validation"
|
|
1507
|
+
}
|
|
1508
|
+
},
|
|
1509
|
+
400
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
state.cardTokenCounter += 1;
|
|
1513
|
+
const token = {
|
|
1514
|
+
id: `ctok_sim_${state.cardTokenCounter}`,
|
|
1515
|
+
pan,
|
|
1516
|
+
last4: pan.slice(-4),
|
|
1517
|
+
expiryMonth: month,
|
|
1518
|
+
expiryYear: year
|
|
1519
|
+
};
|
|
1520
|
+
state.cardTokens.set(token.id, token);
|
|
1521
|
+
return json({ id: token.id });
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1228
1524
|
// src/simulator/payments.ts
|
|
1229
1525
|
var import_node_crypto5 = __toESM(require("crypto"));
|
|
1230
1526
|
|
|
@@ -1252,7 +1548,7 @@ var BEAM_BOLT_PAYMENT_METHODS = [
|
|
|
1252
1548
|
"SPAY_LATER"
|
|
1253
1549
|
];
|
|
1254
1550
|
var BEAM_BOLT_INTENT_STATUSES = ["PENDING", "PAID", "FAILED", "CANCELED", "EXPIRED"];
|
|
1255
|
-
var
|
|
1551
|
+
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;
|
|
1256
1552
|
function payErr(error, status = 400, code, details) {
|
|
1257
1553
|
const body = { error };
|
|
1258
1554
|
if (code) body.code = code;
|
|
@@ -1266,6 +1562,12 @@ function paymentOperationForRequest(request, pathname) {
|
|
|
1266
1562
|
if (request.method === "GET" && /^\/api\/store\/beam\/[^/]+\/payment-links\/[^/]+$/.test(pathname)) {
|
|
1267
1563
|
return "beam.getPaymentLinkStatus";
|
|
1268
1564
|
}
|
|
1565
|
+
if (request.method === "POST" && /^\/api\/store\/beam\/[^/]+\/charges$/.test(pathname)) {
|
|
1566
|
+
return "beam.createDirectCharge";
|
|
1567
|
+
}
|
|
1568
|
+
if (request.method === "GET" && /^\/api\/store\/beam\/[^/]+\/charges\/[^/]+$/.test(pathname)) {
|
|
1569
|
+
return "beam.getCharge";
|
|
1570
|
+
}
|
|
1269
1571
|
if (request.method === "POST" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout$/.test(pathname)) {
|
|
1270
1572
|
return "stripe.createCheckout";
|
|
1271
1573
|
}
|
|
@@ -1406,6 +1708,8 @@ async function handlePaymentsRequest(request, url) {
|
|
|
1406
1708
|
const pathname = url.pathname;
|
|
1407
1709
|
const injectedFailure = consumePaymentFailure(request, pathname);
|
|
1408
1710
|
if (injectedFailure) return injectedFailure;
|
|
1711
|
+
const directChargeResponse = await handleDirectChargeRequest(request, pathname);
|
|
1712
|
+
if (directChargeResponse) return directChargeResponse;
|
|
1409
1713
|
if (/\/billing-portal$/.test(pathname)) {
|
|
1410
1714
|
return payErr("Not found", 404, "NOT_FOUND");
|
|
1411
1715
|
}
|
|
@@ -1502,7 +1806,7 @@ async function handlePaymentsRequest(request, url) {
|
|
|
1502
1806
|
code: "too_small"
|
|
1503
1807
|
});
|
|
1504
1808
|
}
|
|
1505
|
-
if (typeof body.deploymentId !== "string" || !
|
|
1809
|
+
if (typeof body.deploymentId !== "string" || !UUID_RE2.test(body.deploymentId)) {
|
|
1506
1810
|
issues.push({
|
|
1507
1811
|
path: ["deploymentId"],
|
|
1508
1812
|
message: "Invalid uuid",
|
|
@@ -2247,6 +2551,7 @@ async function handleEdgeRequest(request, subPath) {
|
|
|
2247
2551
|
}
|
|
2248
2552
|
|
|
2249
2553
|
// src/simulator/starlens.ts
|
|
2554
|
+
var LATEST_VERSION = "v2";
|
|
2250
2555
|
function starlensFailure(error, status, code) {
|
|
2251
2556
|
return json({ success: false, error, code }, status);
|
|
2252
2557
|
}
|
|
@@ -2265,11 +2570,18 @@ function nullFlagged(output, issues) {
|
|
|
2265
2570
|
}
|
|
2266
2571
|
return copy;
|
|
2267
2572
|
}
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2573
|
+
function isInputShape(input) {
|
|
2574
|
+
if (!input || typeof input !== "object") return false;
|
|
2575
|
+
const i = input;
|
|
2576
|
+
return typeof i.url === "string" || typeof i.data === "string" && i.data.length > 0 && typeof i.mediaType === "string";
|
|
2577
|
+
}
|
|
2578
|
+
var NON_IMAGE_EXTENSION = /\.(pdf|mp3|m4a|wav|ogg|aac|flac|mp4|mov|webm|mkv)(\?|#|$)/i;
|
|
2579
|
+
function isCroppable(input) {
|
|
2580
|
+
if (typeof input.mediaType === "string") return input.mediaType.startsWith("image/");
|
|
2581
|
+
const name = typeof input.filename === "string" ? input.filename : input.url;
|
|
2582
|
+
return !(typeof name === "string" && NON_IMAGE_EXTENSION.test(name));
|
|
2583
|
+
}
|
|
2584
|
+
function validateStarlensBody(body) {
|
|
2273
2585
|
const inputs = Array.isArray(body.inputs) ? body.inputs : [];
|
|
2274
2586
|
if (inputs.length === 0) {
|
|
2275
2587
|
return starlensFailure(
|
|
@@ -2278,6 +2590,13 @@ async function handleStarlensExtract(request, signedDeploymentId) {
|
|
|
2278
2590
|
"VALIDATION_ERROR"
|
|
2279
2591
|
);
|
|
2280
2592
|
}
|
|
2593
|
+
if (!inputs.every(isInputShape)) {
|
|
2594
|
+
return starlensFailure(
|
|
2595
|
+
"Validation failed \u2014 inputs: each input needs a url, or data with a mediaType",
|
|
2596
|
+
400,
|
|
2597
|
+
"VALIDATION_ERROR"
|
|
2598
|
+
);
|
|
2599
|
+
}
|
|
2281
2600
|
const schema = body.schema;
|
|
2282
2601
|
if (!schema || schema.type !== "object" || typeof schema.properties !== "object") {
|
|
2283
2602
|
return starlensFailure(
|
|
@@ -2286,8 +2605,35 @@ async function handleStarlensExtract(request, signedDeploymentId) {
|
|
|
2286
2605
|
"VALIDATION_ERROR"
|
|
2287
2606
|
);
|
|
2288
2607
|
}
|
|
2608
|
+
const version = body.version ?? "latest";
|
|
2609
|
+
if (version !== "v1" && version !== "v2" && version !== "latest") {
|
|
2610
|
+
return starlensFailure("Validation failed \u2014 version: unknown version", 400, "VALIDATION_ERROR");
|
|
2611
|
+
}
|
|
2612
|
+
const effort = body.effort ?? "low";
|
|
2613
|
+
if (effort !== "low" && effort !== "high") {
|
|
2614
|
+
return starlensFailure("Validation failed \u2014 effort: low or high", 400, "VALIDATION_ERROR");
|
|
2615
|
+
}
|
|
2616
|
+
const resolved = version === "latest" ? LATEST_VERSION : version;
|
|
2617
|
+
if (effort === "high" && resolved === "v1") {
|
|
2618
|
+
return starlensFailure(
|
|
2619
|
+
`Starlens v1 does not support effort "high"; use version "v2"`,
|
|
2620
|
+
400,
|
|
2621
|
+
"unsupported_effort"
|
|
2622
|
+
);
|
|
2623
|
+
}
|
|
2624
|
+
return { version, resolved, effort, inputs, schema };
|
|
2625
|
+
}
|
|
2626
|
+
async function handleStarlensExtract(request, signedDeploymentId) {
|
|
2627
|
+
const body = await readJsonBody(request);
|
|
2628
|
+
const valid = validateStarlensBody(body);
|
|
2629
|
+
if (valid instanceof Response) return valid;
|
|
2630
|
+
const { version, resolved, effort, inputs, schema } = valid;
|
|
2631
|
+
if (signedDeploymentId !== null && body.deploymentId !== signedDeploymentId) {
|
|
2632
|
+
return starlensFailure("Deployment ID mismatch", 401, "AUTHENTICATION_ERROR");
|
|
2633
|
+
}
|
|
2289
2634
|
const call = {
|
|
2290
|
-
version
|
|
2635
|
+
version,
|
|
2636
|
+
effort,
|
|
2291
2637
|
inputs: inputs.map((i) => ({
|
|
2292
2638
|
name: typeof i.filename === "string" ? i.filename : typeof i.url === "string" ? i.url : "upload",
|
|
2293
2639
|
mediaType: typeof i.mediaType === "string" ? i.mediaType : null,
|
|
@@ -2309,8 +2655,10 @@ async function handleStarlensExtract(request, signedDeploymentId) {
|
|
|
2309
2655
|
return starlensFailure(reply.reject, 422, "unsupported_document");
|
|
2310
2656
|
}
|
|
2311
2657
|
const issues = reply.issues ?? [];
|
|
2658
|
+
const ran = reply.effort ?? (effort === "high" && !inputs.some(isCroppable) ? "low" : effort);
|
|
2312
2659
|
return success({
|
|
2313
|
-
version:
|
|
2660
|
+
version: resolved,
|
|
2661
|
+
effort: ran,
|
|
2314
2662
|
output: nullFlagged(reply.output, issues),
|
|
2315
2663
|
issues,
|
|
2316
2664
|
usage: { inputTokens: 0, outputTokens: 0, cost: 0 }
|
|
@@ -2386,6 +2734,9 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2386
2734
|
if (isStorageHost) {
|
|
2387
2735
|
return handleStorageRequest(request, url);
|
|
2388
2736
|
}
|
|
2737
|
+
if (url.hostname === SIM_BEAM_HOST) {
|
|
2738
|
+
return handleCardTokenRequest(request, url);
|
|
2739
|
+
}
|
|
2389
2740
|
if (isEmail && request.method === "POST") {
|
|
2390
2741
|
return handleEmailSend(request);
|
|
2391
2742
|
}
|