@stardeck-customer-apps/testing 0.3.1 → 0.5.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 +65 -0
- package/dist/index.d.mts +212 -67
- package/dist/index.d.ts +212 -67
- package/dist/index.js +990 -233
- package/dist/index.mjs +988 -235
- package/dist/next/headers-shim.js +17 -1
- package/dist/next/headers-shim.mjs +17 -1
- package/dist/setup.js +683 -108
- package/dist/setup.mjs +683 -108
- package/package.json +17 -3
package/dist/setup.js
CHANGED
|
@@ -40,7 +40,23 @@ var state = globalSingleton("state", () => ({
|
|
|
40
40
|
emailCounter: 0,
|
|
41
41
|
identities: /* @__PURE__ */ new Map(),
|
|
42
42
|
identityLinks: [],
|
|
43
|
-
|
|
43
|
+
checkouts: [],
|
|
44
|
+
checkoutCounter: 0,
|
|
45
|
+
sessionStatuses: /* @__PURE__ */ new Map(),
|
|
46
|
+
paymentLinks: /* @__PURE__ */ new Map(),
|
|
47
|
+
products: [],
|
|
48
|
+
uploads: [],
|
|
49
|
+
uploadCounter: 0,
|
|
50
|
+
storageFiles: /* @__PURE__ */ new Map(),
|
|
51
|
+
presignPending: /* @__PURE__ */ new Map(),
|
|
52
|
+
messages: [],
|
|
53
|
+
edgePrints: [],
|
|
54
|
+
edgeDisplays: [],
|
|
55
|
+
edgeTestPrints: [],
|
|
56
|
+
edgePrintCounter: 0,
|
|
57
|
+
edgeDevices: [],
|
|
58
|
+
edgePeripherals: [],
|
|
59
|
+
edgeBindings: /* @__PURE__ */ new Map(),
|
|
44
60
|
allowNetwork: false
|
|
45
61
|
}));
|
|
46
62
|
function requireDb() {
|
|
@@ -56,13 +72,16 @@ function requireDb() {
|
|
|
56
72
|
var TEST_DOMAIN_SUFFIX = ".stardeck.test";
|
|
57
73
|
var CONTROL_PLANE_TEST_URL = "https://control-plane.stardeck.test";
|
|
58
74
|
var DATA_STORE_TEST_HOST = "db.stardeck.test";
|
|
75
|
+
var STORAGE_TEST_URL = "https://storage.stardeck.test";
|
|
76
|
+
var STORAGE_TEST_HOST = "storage.stardeck.test";
|
|
59
77
|
var TEST_ENV_DEFAULTS = {
|
|
60
78
|
CONTROL_PLANE_URL: CONTROL_PLANE_TEST_URL,
|
|
61
79
|
DEPLOYMENT_SECRET: "stardeck-test-deployment-secret",
|
|
62
80
|
ORGANIZATION_ID: "00000000-0000-4000-8000-00000000000a",
|
|
63
81
|
PROJECT_ID: "00000000-0000-4000-8000-00000000000b",
|
|
64
82
|
DEPLOYMENT_ID: "00000000-0000-4000-8000-00000000000c",
|
|
65
|
-
DATA_STORE_URL: `postgresql://test:test@${DATA_STORE_TEST_HOST}/main
|
|
83
|
+
DATA_STORE_URL: `postgresql://test:test@${DATA_STORE_TEST_HOST}/main`,
|
|
84
|
+
STORAGE_URL: STORAGE_TEST_URL
|
|
66
85
|
};
|
|
67
86
|
|
|
68
87
|
// src/simulator/hmac.ts
|
|
@@ -92,8 +111,8 @@ function verifyDeploymentAuthHeader(secret, header) {
|
|
|
92
111
|
return null;
|
|
93
112
|
}
|
|
94
113
|
if (payload.type !== "deployment-request") return null;
|
|
95
|
-
const
|
|
96
|
-
if (Math.abs(
|
|
114
|
+
const now3 = Math.floor(Date.now() / 1e3);
|
|
115
|
+
if (Math.abs(now3 - payload.timestamp) > TIMESTAMP_TOLERANCE_SECONDS) return null;
|
|
97
116
|
return payload;
|
|
98
117
|
}
|
|
99
118
|
|
|
@@ -110,6 +129,13 @@ function success(data) {
|
|
|
110
129
|
function failure(error, status = 400) {
|
|
111
130
|
return json({ success: false, error }, status);
|
|
112
131
|
}
|
|
132
|
+
async function readJsonBody(request) {
|
|
133
|
+
try {
|
|
134
|
+
return await request.json();
|
|
135
|
+
} catch {
|
|
136
|
+
return {};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
113
139
|
|
|
114
140
|
// src/simulator/data-store.ts
|
|
115
141
|
function quoteIdent(name) {
|
|
@@ -590,13 +616,6 @@ function now() {
|
|
|
590
616
|
function linksFor(identityId) {
|
|
591
617
|
return state.identityLinks.filter((l) => l.identityId === identityId);
|
|
592
618
|
}
|
|
593
|
-
async function readBody(request) {
|
|
594
|
-
try {
|
|
595
|
-
return await request.json();
|
|
596
|
-
} catch {
|
|
597
|
-
return {};
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
619
|
function handleList(request) {
|
|
601
620
|
const typeParam = new URL(request.url).searchParams.get("type");
|
|
602
621
|
const type = typeParam === "person" || typeParam === "account" ? typeParam : void 0;
|
|
@@ -604,7 +623,7 @@ function handleList(request) {
|
|
|
604
623
|
return success({ identities: type ? identities.filter((i) => i.type === type) : identities });
|
|
605
624
|
}
|
|
606
625
|
async function handleCreate(request) {
|
|
607
|
-
const body = await
|
|
626
|
+
const body = await readJsonBody(request);
|
|
608
627
|
const type = body.type;
|
|
609
628
|
if (type !== "person" && type !== "account") {
|
|
610
629
|
return failure("type must be 'person' or 'account'");
|
|
@@ -641,7 +660,7 @@ function handleGet(identityId) {
|
|
|
641
660
|
async function handleUpdate(identityId, request) {
|
|
642
661
|
const identity = state.identities.get(identityId);
|
|
643
662
|
if (!identity) return failure("identity not found", 404);
|
|
644
|
-
const body = await
|
|
663
|
+
const body = await readJsonBody(request);
|
|
645
664
|
if (body.displayName !== void 0) {
|
|
646
665
|
identity.displayName = body.displayName;
|
|
647
666
|
}
|
|
@@ -663,7 +682,7 @@ async function handleAttachLink(identityId, request) {
|
|
|
663
682
|
if (identity.status !== "active") {
|
|
664
683
|
return failure("links attach only to active persons");
|
|
665
684
|
}
|
|
666
|
-
const body = await
|
|
685
|
+
const body = await readJsonBody(request);
|
|
667
686
|
const kind = body.kind;
|
|
668
687
|
const externalId = body.externalId;
|
|
669
688
|
if (typeof kind !== "string" || !LINK_KINDS.has(kind)) {
|
|
@@ -688,114 +707,635 @@ async function handleAttachLink(identityId, request) {
|
|
|
688
707
|
state.identityLinks.push(link);
|
|
689
708
|
return success({ link });
|
|
690
709
|
}
|
|
691
|
-
function
|
|
710
|
+
async function handleIdentitiesRequest(request, subPath) {
|
|
711
|
+
const method = request.method;
|
|
712
|
+
if (subPath === "" || subPath === "/") {
|
|
713
|
+
if (method === "GET") return handleList(request);
|
|
714
|
+
if (method === "POST") return handleCreate(request);
|
|
715
|
+
}
|
|
716
|
+
const linksMatch = subPath.match(/^\/([^/]+)\/links$/);
|
|
717
|
+
if (linksMatch && method === "POST") {
|
|
718
|
+
return handleAttachLink(linksMatch[1], request);
|
|
719
|
+
}
|
|
720
|
+
const singleMatch = subPath.match(/^\/([^/]+)$/);
|
|
721
|
+
if (singleMatch) {
|
|
722
|
+
if (method === "GET") return handleGet(singleMatch[1]);
|
|
723
|
+
if (method === "PATCH") return handleUpdate(singleMatch[1], request);
|
|
724
|
+
}
|
|
725
|
+
return failure(`No identities simulator for ${method} .../identities${subPath}`, 404);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// src/simulator/payments.ts
|
|
729
|
+
var import_node_crypto5 = __toESM(require("crypto"));
|
|
730
|
+
|
|
731
|
+
// src/next/headers-shim.ts
|
|
732
|
+
var import_node_async_hooks = require("async_hooks");
|
|
733
|
+
var requestScopeStorage = globalSingleton(
|
|
734
|
+
"request-scope",
|
|
735
|
+
() => new import_node_async_hooks.AsyncLocalStorage()
|
|
736
|
+
);
|
|
737
|
+
|
|
738
|
+
// src/simulator/webhook-signing.ts
|
|
739
|
+
var import_node_crypto4 = __toESM(require("crypto"));
|
|
740
|
+
|
|
741
|
+
// src/simulator/payments.ts
|
|
742
|
+
function payErr(error, status = 400, code) {
|
|
743
|
+
return json(code ? { error, code } : { error }, status);
|
|
744
|
+
}
|
|
745
|
+
function nextCheckoutId() {
|
|
746
|
+
state.checkoutCounter += 1;
|
|
747
|
+
return `cs_test_${state.checkoutCounter}`;
|
|
748
|
+
}
|
|
749
|
+
function nextPaymentLinkId() {
|
|
750
|
+
state.checkoutCounter += 1;
|
|
751
|
+
return `plink_test_${state.checkoutCounter}`;
|
|
752
|
+
}
|
|
753
|
+
function seedStripeSession(id, body) {
|
|
754
|
+
const lineItems = body.lineItems ?? [];
|
|
755
|
+
let amountTotal = null;
|
|
756
|
+
let currency = null;
|
|
757
|
+
if (lineItems.length > 0) {
|
|
758
|
+
amountTotal = lineItems.reduce(
|
|
759
|
+
(sum, item) => sum + (item.priceData?.unitAmount ?? 0) * (item.quantity ?? 1),
|
|
760
|
+
0
|
|
761
|
+
);
|
|
762
|
+
currency = lineItems[0].priceData?.currency ?? null;
|
|
763
|
+
}
|
|
764
|
+
state.sessionStatuses.set(id, {
|
|
765
|
+
id,
|
|
766
|
+
status: "open",
|
|
767
|
+
paymentStatus: "unpaid",
|
|
768
|
+
mode: body.mode ?? "payment",
|
|
769
|
+
amountTotal,
|
|
770
|
+
currency,
|
|
771
|
+
customerEmail: body.customerEmail ? String(body.customerEmail) : null,
|
|
772
|
+
metadata: body.metadata ?? {},
|
|
773
|
+
expiresAt: Math.floor(Date.now() / 1e3) + 3600
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
function seedBeamLink(id, body, merchantId) {
|
|
777
|
+
const order = body.order;
|
|
778
|
+
state.paymentLinks.set(id, {
|
|
779
|
+
paymentLinkId: id,
|
|
780
|
+
merchantId,
|
|
781
|
+
url: `https://beam.test/pay/${id}`,
|
|
782
|
+
status: "ACTIVE",
|
|
783
|
+
order: {
|
|
784
|
+
netAmount: Number(order?.netAmount ?? 0),
|
|
785
|
+
currency: String(order?.currency ?? "THB"),
|
|
786
|
+
description: String(order?.description ?? ""),
|
|
787
|
+
referenceId: order?.referenceId ? String(order.referenceId) : void 0,
|
|
788
|
+
internalNote: order?.internalNote ? String(order.internalNote) : void 0,
|
|
789
|
+
orderItems: order?.orderItems
|
|
790
|
+
},
|
|
791
|
+
redirectUrl: body.redirectUrl ? String(body.redirectUrl) : void 0,
|
|
792
|
+
linkSettings: body.linkSettings,
|
|
793
|
+
collectDeliveryAddress: body.collectDeliveryAddress === true
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
async function handlePaymentsRequest(request, url) {
|
|
797
|
+
const pathname = url.pathname;
|
|
798
|
+
if (/\/bolt-connections/.test(pathname) || /\/bolt-intents/.test(pathname) || /\/charges(\/|$)/.test(pathname) || /\/billing-portal$/.test(pathname)) {
|
|
799
|
+
return payErr("Not found", 404, "NOT_FOUND");
|
|
800
|
+
}
|
|
801
|
+
const beamProductsMatch = pathname.match(
|
|
802
|
+
/^\/api\/store\/beam\/([^/]+)\/payment-links(?:\/([^/]+))?$/
|
|
803
|
+
);
|
|
804
|
+
if (beamProductsMatch) {
|
|
805
|
+
const merchantId = beamProductsMatch[1];
|
|
806
|
+
const linkId = beamProductsMatch[2];
|
|
807
|
+
if (!linkId && request.method === "POST") {
|
|
808
|
+
const body = await readJsonBody(request);
|
|
809
|
+
const id = nextPaymentLinkId();
|
|
810
|
+
const checkoutUrl = `https://beam.test/pay/${id}`;
|
|
811
|
+
const captured = {
|
|
812
|
+
id,
|
|
813
|
+
url: checkoutUrl,
|
|
814
|
+
provider: "beam",
|
|
815
|
+
options: body,
|
|
816
|
+
metadata: body.metadata,
|
|
817
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
818
|
+
};
|
|
819
|
+
state.checkouts.push(captured);
|
|
820
|
+
seedBeamLink(id, body, merchantId);
|
|
821
|
+
return json({ id, url: checkoutUrl });
|
|
822
|
+
}
|
|
823
|
+
if (linkId && request.method === "GET") {
|
|
824
|
+
const link = state.paymentLinks.get(linkId);
|
|
825
|
+
if (!link) return payErr("Payment link not found", 404, "NOT_FOUND");
|
|
826
|
+
return json({ paymentLink: link });
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
const stripeStoreMatch = pathname.match(/^\/api\/store\/([^/]+)\/(.+)$/);
|
|
830
|
+
if (stripeStoreMatch) {
|
|
831
|
+
const accountId = stripeStoreMatch[1];
|
|
832
|
+
const subPath = stripeStoreMatch[2];
|
|
833
|
+
if (accountId === "beam") {
|
|
834
|
+
return payErr(`No payments simulator for ${request.method} ${pathname}`, 404);
|
|
835
|
+
}
|
|
836
|
+
if (subPath === "products" && request.method === "GET") {
|
|
837
|
+
return json({ products: state.products });
|
|
838
|
+
}
|
|
839
|
+
const productMatch = subPath.match(/^products\/([^/]+)$/);
|
|
840
|
+
if (productMatch && request.method === "GET") {
|
|
841
|
+
const product = state.products.find((p) => p.id === productMatch[1]);
|
|
842
|
+
if (!product) return payErr("Product not found", 404, "NOT_FOUND");
|
|
843
|
+
return json({ product });
|
|
844
|
+
}
|
|
845
|
+
if (subPath === "checkout" && request.method === "POST") {
|
|
846
|
+
const body = await readJsonBody(request);
|
|
847
|
+
const id = nextCheckoutId();
|
|
848
|
+
const checkoutUrl = `https://checkout.stripe.test/c/pay/${id}`;
|
|
849
|
+
const captured = {
|
|
850
|
+
id,
|
|
851
|
+
url: checkoutUrl,
|
|
852
|
+
provider: "stripe",
|
|
853
|
+
options: body,
|
|
854
|
+
mode: body.mode,
|
|
855
|
+
metadata: body.metadata,
|
|
856
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
857
|
+
};
|
|
858
|
+
state.checkouts.push(captured);
|
|
859
|
+
seedStripeSession(id, body);
|
|
860
|
+
return json({ id, url: checkoutUrl });
|
|
861
|
+
}
|
|
862
|
+
const sessionMatch = subPath.match(/^checkout-sessions\/([^/]+)$/);
|
|
863
|
+
if (sessionMatch && request.method === "GET") {
|
|
864
|
+
const session = state.sessionStatuses.get(sessionMatch[1]);
|
|
865
|
+
if (!session) return payErr("Checkout session not found", 404, "NOT_FOUND");
|
|
866
|
+
return json({ session });
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
return payErr(`No payments simulator for ${request.method} ${pathname}`, 404);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// src/simulator/storage.ts
|
|
873
|
+
var import_node_crypto6 = __toESM(require("crypto"));
|
|
874
|
+
function storageErr(error, status = 400) {
|
|
875
|
+
return json({ error }, status);
|
|
876
|
+
}
|
|
877
|
+
function fileExtension(filename) {
|
|
878
|
+
const dot = filename.lastIndexOf(".");
|
|
879
|
+
return dot === -1 ? "bin" : filename.slice(dot + 1);
|
|
880
|
+
}
|
|
881
|
+
function buildKey(fileId, filename) {
|
|
882
|
+
const orgId = process.env.ORGANIZATION_ID ?? TEST_ENV_DEFAULTS.ORGANIZATION_ID;
|
|
883
|
+
const projectId = process.env.PROJECT_ID ?? TEST_ENV_DEFAULTS.PROJECT_ID;
|
|
884
|
+
const deploymentId = process.env.DEPLOYMENT_ID ?? TEST_ENV_DEFAULTS.DEPLOYMENT_ID;
|
|
885
|
+
return `${orgId}/${projectId}/${deploymentId}/${fileId}.${fileExtension(filename)}`;
|
|
886
|
+
}
|
|
887
|
+
function buildUrl(key) {
|
|
888
|
+
return `${STORAGE_TEST_URL}/files/${key}`;
|
|
889
|
+
}
|
|
890
|
+
function toUploadResponse(record) {
|
|
692
891
|
return {
|
|
693
|
-
id:
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
892
|
+
id: record.id,
|
|
893
|
+
key: record.key,
|
|
894
|
+
filename: record.filename,
|
|
895
|
+
contentType: record.contentType,
|
|
896
|
+
sizeBytes: record.sizeBytes,
|
|
897
|
+
url: record.url,
|
|
898
|
+
uploadedAt: record.uploadedAt,
|
|
899
|
+
isPublic: record.isPublic,
|
|
900
|
+
metadata: record.metadata
|
|
699
901
|
};
|
|
700
902
|
}
|
|
701
|
-
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
903
|
+
function captureUpload(record, method, path) {
|
|
904
|
+
const captured = {
|
|
905
|
+
id: record.id,
|
|
906
|
+
key: record.key,
|
|
907
|
+
filename: record.filename,
|
|
908
|
+
contentType: record.contentType,
|
|
909
|
+
sizeBytes: record.sizeBytes,
|
|
910
|
+
url: record.url,
|
|
911
|
+
uploadedAt: record.uploadedAt,
|
|
912
|
+
isPublic: record.isPublic,
|
|
913
|
+
metadata: record.metadata,
|
|
914
|
+
method,
|
|
915
|
+
path
|
|
916
|
+
};
|
|
917
|
+
state.uploads.push(captured);
|
|
918
|
+
}
|
|
919
|
+
function createFileRecord(input) {
|
|
920
|
+
state.uploadCounter += 1;
|
|
921
|
+
const id = `file_test_${state.uploadCounter}`;
|
|
922
|
+
const key = buildKey(id, input.filename);
|
|
923
|
+
return {
|
|
924
|
+
id,
|
|
925
|
+
key,
|
|
926
|
+
filename: input.filename,
|
|
927
|
+
contentType: input.contentType,
|
|
928
|
+
sizeBytes: input.sizeBytes,
|
|
929
|
+
url: buildUrl(key),
|
|
930
|
+
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
931
|
+
metadata: input.metadata,
|
|
932
|
+
isPublic: input.isPublic,
|
|
933
|
+
deleted: false
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
function activeFiles() {
|
|
937
|
+
return [...state.storageFiles.values()].filter((f) => !f.deleted);
|
|
938
|
+
}
|
|
939
|
+
async function handleStorageRequest(request, url) {
|
|
940
|
+
const pathname = url.pathname;
|
|
941
|
+
if (pathname.startsWith("/upload/presign/multipart")) {
|
|
942
|
+
return storageErr("Not found", 404);
|
|
943
|
+
}
|
|
944
|
+
const presignedPutMatch = pathname.match(/^\/presigned\/([^/]+)$/);
|
|
945
|
+
if (presignedPutMatch && request.method === "PUT") {
|
|
946
|
+
const fileId = presignedPutMatch[1];
|
|
947
|
+
if (!state.presignPending.has(fileId)) {
|
|
948
|
+
return storageErr("Presign session not found", 404);
|
|
949
|
+
}
|
|
950
|
+
return new Response(null, {
|
|
951
|
+
status: 200,
|
|
952
|
+
headers: { etag: `"${import_node_crypto6.default.randomUUID()}"` }
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
if (pathname === "/upload/presign/complete" && request.method === "POST") {
|
|
956
|
+
const body = await readJsonBody(request);
|
|
957
|
+
const fileId = String(body.fileId ?? "");
|
|
958
|
+
const pending = state.presignPending.get(fileId);
|
|
959
|
+
if (!pending) return storageErr("Presign session not found", 404);
|
|
960
|
+
const record = {
|
|
961
|
+
id: fileId,
|
|
962
|
+
key: pending.key,
|
|
963
|
+
filename: pending.fileName,
|
|
964
|
+
contentType: pending.contentType,
|
|
965
|
+
sizeBytes: pending.sizeBytes,
|
|
966
|
+
url: buildUrl(pending.key),
|
|
967
|
+
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
968
|
+
metadata: pending.metadata,
|
|
969
|
+
isPublic: pending.isPublic,
|
|
970
|
+
deleted: false
|
|
971
|
+
};
|
|
972
|
+
state.storageFiles.set(record.id, record);
|
|
973
|
+
state.presignPending.delete(fileId);
|
|
974
|
+
captureUpload(record, "POST", "/upload/presign/complete");
|
|
975
|
+
return json(toUploadResponse(record));
|
|
976
|
+
}
|
|
977
|
+
if (pathname === "/upload/presign" && request.method === "POST") {
|
|
978
|
+
const body = await readJsonBody(request);
|
|
979
|
+
const fileName = String(body.fileName ?? "upload.bin");
|
|
980
|
+
const contentType = String(body.contentType ?? "application/octet-stream");
|
|
981
|
+
const sizeBytes = Number(body.sizeBytes ?? 0);
|
|
982
|
+
const isPublic = body.isPublic !== false;
|
|
983
|
+
const metadata = body.metadata;
|
|
984
|
+
state.uploadCounter += 1;
|
|
985
|
+
const fileId = `file_test_${state.uploadCounter}`;
|
|
986
|
+
const key = buildKey(fileId, fileName);
|
|
987
|
+
state.presignPending.set(fileId, {
|
|
988
|
+
fileName,
|
|
989
|
+
contentType,
|
|
990
|
+
sizeBytes,
|
|
991
|
+
isPublic,
|
|
992
|
+
metadata,
|
|
993
|
+
key
|
|
994
|
+
});
|
|
995
|
+
return json({
|
|
996
|
+
fileId,
|
|
997
|
+
key,
|
|
998
|
+
presignedUrl: `${STORAGE_TEST_URL}/presigned/${fileId}`,
|
|
999
|
+
contentType,
|
|
1000
|
+
expiresIn: 3600
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
if (pathname === "/upload" && request.method === "POST") {
|
|
1004
|
+
const isPublic = url.searchParams.get("isPublic") !== "false";
|
|
1005
|
+
const formData = await request.formData();
|
|
1006
|
+
const file = formData.get("file");
|
|
1007
|
+
if (!(file instanceof Blob)) {
|
|
1008
|
+
return storageErr("file is required");
|
|
1009
|
+
}
|
|
1010
|
+
const filename = formData.get("filename") ?? (file instanceof File ? file.name : "upload.bin");
|
|
1011
|
+
const metadataRaw = formData.get("metadata");
|
|
1012
|
+
let metadata;
|
|
1013
|
+
if (typeof metadataRaw === "string" && metadataRaw) {
|
|
1014
|
+
try {
|
|
1015
|
+
metadata = JSON.parse(metadataRaw);
|
|
1016
|
+
} catch {
|
|
1017
|
+
return storageErr("metadata must be valid JSON");
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
const record = createFileRecord({
|
|
1021
|
+
filename,
|
|
1022
|
+
contentType: file.type || "application/octet-stream",
|
|
1023
|
+
sizeBytes: file.size,
|
|
1024
|
+
isPublic,
|
|
1025
|
+
metadata
|
|
1026
|
+
});
|
|
1027
|
+
state.storageFiles.set(record.id, record);
|
|
1028
|
+
captureUpload(record, "POST", "/upload");
|
|
1029
|
+
return json(toUploadResponse(record));
|
|
1030
|
+
}
|
|
1031
|
+
const fileMatch = pathname.match(/^\/api\/files\/([^/]+)$/);
|
|
1032
|
+
if (fileMatch) {
|
|
1033
|
+
const fileId = fileMatch[1];
|
|
1034
|
+
const record = state.storageFiles.get(fileId);
|
|
1035
|
+
if (!record || record.deleted) {
|
|
1036
|
+
return storageErr("File not found", 404);
|
|
1037
|
+
}
|
|
1038
|
+
if (request.method === "GET") {
|
|
1039
|
+
return json(toUploadResponse(record));
|
|
1040
|
+
}
|
|
1041
|
+
if (request.method === "PATCH") {
|
|
1042
|
+
const body = await readJsonBody(request);
|
|
1043
|
+
if (body.fileName !== void 0) record.filename = String(body.fileName);
|
|
1044
|
+
if (body.metadata !== void 0) {
|
|
1045
|
+
record.metadata = body.metadata;
|
|
1046
|
+
}
|
|
1047
|
+
if (body.isPublic !== void 0) record.isPublic = body.isPublic === true;
|
|
1048
|
+
return json(toUploadResponse(record));
|
|
1049
|
+
}
|
|
1050
|
+
if (request.method === "DELETE") {
|
|
1051
|
+
record.deleted = true;
|
|
1052
|
+
return json({ success: true });
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
if (pathname === "/api/files" && request.method === "GET") {
|
|
1056
|
+
const parseParam = (raw, fallback, min) => {
|
|
1057
|
+
const n = Number(raw);
|
|
1058
|
+
return Number.isFinite(n) && n >= min ? Math.floor(n) : fallback;
|
|
1059
|
+
};
|
|
1060
|
+
const limit = parseParam(url.searchParams.get("limit"), 50, 1);
|
|
1061
|
+
const offset = parseParam(url.searchParams.get("offset"), 0, 0);
|
|
1062
|
+
const prefix = url.searchParams.get("prefix") ?? "";
|
|
1063
|
+
let files = activeFiles();
|
|
1064
|
+
if (prefix) {
|
|
1065
|
+
files = files.filter((f) => f.key.startsWith(prefix) || f.filename.startsWith(prefix));
|
|
1066
|
+
}
|
|
1067
|
+
const total = files.length;
|
|
1068
|
+
const slice = files.slice(offset, offset + limit);
|
|
1069
|
+
return json({
|
|
1070
|
+
files: slice.map((f) => toUploadResponse(f)),
|
|
1071
|
+
total,
|
|
1072
|
+
limit,
|
|
1073
|
+
offset,
|
|
1074
|
+
hasMore: offset + slice.length < total
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
return storageErr(`No storage simulator for ${request.method} ${pathname}`, 404);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// src/simulator/messaging.ts
|
|
1081
|
+
var import_node_crypto7 = __toESM(require("crypto"));
|
|
1082
|
+
function captureMessage(channel, recipient, body, connectionId) {
|
|
1083
|
+
const message = {
|
|
1084
|
+
channel,
|
|
1085
|
+
recipient,
|
|
1086
|
+
body: {
|
|
1087
|
+
text: body.text ? String(body.text) : void 0,
|
|
1088
|
+
blocks: body.blocks,
|
|
1089
|
+
threadTs: body.threadTs ? String(body.threadTs) : void 0,
|
|
1090
|
+
messagingType: body.messagingType ? String(body.messagingType) : void 0,
|
|
1091
|
+
tag: body.tag ? String(body.tag) : void 0
|
|
1092
|
+
},
|
|
1093
|
+
connectionId,
|
|
1094
|
+
sentAt: /* @__PURE__ */ new Date()
|
|
1095
|
+
};
|
|
1096
|
+
state.messages.push(message);
|
|
1097
|
+
}
|
|
1098
|
+
async function handleMessagingRequest(request, channel, subPath) {
|
|
1099
|
+
const method = request.method;
|
|
1100
|
+
if ((subPath === "" || subPath === "/" || subPath === "/connections") && method === "GET") {
|
|
1101
|
+
return success({ connections: [] });
|
|
706
1102
|
}
|
|
707
|
-
if (
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
1103
|
+
if (channel === "slack" && subPath === "/send" && method === "POST") {
|
|
1104
|
+
const body = await readJsonBody(request);
|
|
1105
|
+
if (!body.text && !body.blocks) {
|
|
1106
|
+
return failure("text or blocks is required");
|
|
1107
|
+
}
|
|
1108
|
+
const slackChannel = String(body.channel ?? "");
|
|
1109
|
+
if (!slackChannel) {
|
|
1110
|
+
return failure("channel is required");
|
|
1111
|
+
}
|
|
1112
|
+
captureMessage(
|
|
1113
|
+
"slack",
|
|
1114
|
+
slackChannel,
|
|
1115
|
+
body,
|
|
1116
|
+
body.connectionId ? String(body.connectionId) : void 0
|
|
711
1117
|
);
|
|
1118
|
+
return success({
|
|
1119
|
+
ok: true,
|
|
1120
|
+
ts: `${Date.now()}.${import_node_crypto7.default.randomUUID().slice(0, 6)}`,
|
|
1121
|
+
channel: slackChannel
|
|
1122
|
+
});
|
|
712
1123
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1124
|
+
if (channel === "line" && subPath === "/push" && method === "POST") {
|
|
1125
|
+
const body = await readJsonBody(request);
|
|
1126
|
+
const userId = String(body.userId ?? "");
|
|
1127
|
+
const message = body.message;
|
|
1128
|
+
if (!userId) return failure("userId is required");
|
|
1129
|
+
if (!message || message.type !== "text" || !message.text) {
|
|
1130
|
+
return failure("message must be { type: 'text', text: string }");
|
|
1131
|
+
}
|
|
1132
|
+
if (message.text.length > 5e3) {
|
|
1133
|
+
return failure("message text exceeds maximum length");
|
|
1134
|
+
}
|
|
1135
|
+
captureMessage(
|
|
1136
|
+
"line",
|
|
1137
|
+
userId,
|
|
1138
|
+
{ text: message.text },
|
|
1139
|
+
body.connectionId ? String(body.connectionId) : void 0
|
|
1140
|
+
);
|
|
1141
|
+
return success({ success: true });
|
|
718
1142
|
}
|
|
719
|
-
if (
|
|
720
|
-
|
|
1143
|
+
if (channel === "facebook" && subPath === "/send" && method === "POST") {
|
|
1144
|
+
const body = await readJsonBody(request);
|
|
1145
|
+
const recipientId = String(body.recipientId ?? "");
|
|
1146
|
+
const message = body.message;
|
|
1147
|
+
if (!recipientId) return failure("recipientId is required");
|
|
1148
|
+
if (!message || message.type !== "text" || !message.text) {
|
|
1149
|
+
return failure("message must be { type: 'text', text: string }");
|
|
1150
|
+
}
|
|
1151
|
+
if (message.text.length > 2e3) {
|
|
1152
|
+
return failure("message text exceeds maximum length");
|
|
1153
|
+
}
|
|
1154
|
+
captureMessage(
|
|
1155
|
+
"facebook",
|
|
1156
|
+
recipientId,
|
|
1157
|
+
{
|
|
1158
|
+
text: message.text,
|
|
1159
|
+
messagingType: body.messagingType ? String(body.messagingType) : void 0,
|
|
1160
|
+
tag: body.tag ? String(body.tag) : void 0
|
|
1161
|
+
},
|
|
1162
|
+
body.connectionId ? String(body.connectionId) : void 0
|
|
1163
|
+
);
|
|
1164
|
+
return success({ success: true });
|
|
721
1165
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
1166
|
+
return failure(`No messaging simulator for ${method} .../integrations/${channel}${subPath}`, 404);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// src/simulator/edge.ts
|
|
1170
|
+
function edgeFailure(error, status = 400, code) {
|
|
1171
|
+
return json({ success: false, error, ...code ? { code } : {} }, status);
|
|
1172
|
+
}
|
|
1173
|
+
function bindingNotFound(alias) {
|
|
1174
|
+
return edgeFailure(`No peripheral is paired to alias "${alias}"`, 404, "BINDING_NOT_FOUND");
|
|
1175
|
+
}
|
|
1176
|
+
function now2() {
|
|
1177
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1178
|
+
}
|
|
1179
|
+
function nextJobId() {
|
|
1180
|
+
state.edgePrintCounter += 1;
|
|
1181
|
+
return `job_${state.edgePrintCounter}`;
|
|
1182
|
+
}
|
|
1183
|
+
function nextConfigVersion() {
|
|
1184
|
+
return state.edgeDisplays.length + 1;
|
|
1185
|
+
}
|
|
1186
|
+
function findPeripheral(id) {
|
|
1187
|
+
return state.edgePeripherals.find((p) => p.id === id);
|
|
1188
|
+
}
|
|
1189
|
+
function buildBinding(alias, peripheralId) {
|
|
1190
|
+
const peripheral = findPeripheral(peripheralId);
|
|
1191
|
+
return {
|
|
1192
|
+
alias,
|
|
1193
|
+
state: peripheral ? "ok" : "peripheral_missing",
|
|
1194
|
+
peripheral: peripheral ? {
|
|
1195
|
+
id: peripheral.id,
|
|
1196
|
+
displayName: peripheral.displayName,
|
|
1197
|
+
driver: peripheral.driver,
|
|
1198
|
+
connected: peripheral.connected
|
|
1199
|
+
} : null,
|
|
1200
|
+
device: peripheral ? {
|
|
1201
|
+
id: peripheral.device.id,
|
|
1202
|
+
displayName: peripheral.device.displayName,
|
|
1203
|
+
status: peripheral.device.status
|
|
1204
|
+
} : null,
|
|
1205
|
+
updatedAt: now2()
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
function handleListDevices() {
|
|
1209
|
+
return success({ devices: [...state.edgeDevices] });
|
|
1210
|
+
}
|
|
1211
|
+
function handleListPeripherals(request) {
|
|
1212
|
+
const deviceId = new URL(request.url).searchParams.get("deviceId");
|
|
1213
|
+
const peripherals = deviceId ? state.edgePeripherals.filter((p) => p.device.id === deviceId) : [...state.edgePeripherals];
|
|
1214
|
+
return success({ peripherals });
|
|
1215
|
+
}
|
|
1216
|
+
function handleListBindings() {
|
|
1217
|
+
return success({ bindings: [...state.edgeBindings.values()] });
|
|
1218
|
+
}
|
|
1219
|
+
function handleGetBinding(alias) {
|
|
1220
|
+
const binding = state.edgeBindings.get(alias);
|
|
1221
|
+
if (!binding) return bindingNotFound(alias);
|
|
1222
|
+
return success({ binding });
|
|
1223
|
+
}
|
|
1224
|
+
async function handlePair(request) {
|
|
1225
|
+
const body = await readJsonBody(request);
|
|
1226
|
+
const alias = String(body.alias ?? "");
|
|
1227
|
+
const peripheralId = String(body.peripheralId ?? "");
|
|
1228
|
+
if (!alias) return edgeFailure("alias is required");
|
|
1229
|
+
if (!peripheralId) return edgeFailure("peripheralId is required");
|
|
1230
|
+
if (!findPeripheral(peripheralId)) {
|
|
1231
|
+
return edgeFailure("Peripheral not found or its device is not granted to this project", 404);
|
|
728
1232
|
}
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
1233
|
+
const binding = buildBinding(alias, peripheralId);
|
|
1234
|
+
state.edgeBindings.set(alias, binding);
|
|
1235
|
+
return success({ binding });
|
|
1236
|
+
}
|
|
1237
|
+
function handleUnpair(alias) {
|
|
1238
|
+
const existed = state.edgeBindings.delete(alias);
|
|
1239
|
+
if (!existed) return bindingNotFound(alias);
|
|
1240
|
+
return success({ deleted: true });
|
|
1241
|
+
}
|
|
1242
|
+
async function handlePrint(request) {
|
|
1243
|
+
const body = await readJsonBody(request);
|
|
1244
|
+
const jobId = nextJobId();
|
|
1245
|
+
const captured = {
|
|
1246
|
+
jobId,
|
|
1247
|
+
deploymentId: String(body.deploymentId ?? ""),
|
|
1248
|
+
alias: body.alias ? String(body.alias) : void 0,
|
|
1249
|
+
deviceId: body.deviceId ? String(body.deviceId) : void 0,
|
|
1250
|
+
peripheralId: body.peripheralId ? String(body.peripheralId) : void 0,
|
|
1251
|
+
receipt: body.receipt ?? {},
|
|
1252
|
+
openDrawer: body.openDrawer === true,
|
|
1253
|
+
logo: body.logo === true ? true : body.logo === false ? false : void 0,
|
|
1254
|
+
copies: typeof body.copies === "number" ? body.copies : 1
|
|
740
1255
|
};
|
|
741
|
-
state.
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
kind,
|
|
746
|
-
externalId,
|
|
747
|
-
verified: true,
|
|
748
|
-
createdAt: now()
|
|
1256
|
+
state.edgePrints.push(captured);
|
|
1257
|
+
return success({
|
|
1258
|
+
jobId,
|
|
1259
|
+
status: "completed"
|
|
749
1260
|
});
|
|
750
|
-
return success({ identity, created: true });
|
|
751
1261
|
}
|
|
752
|
-
async function
|
|
753
|
-
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
source: typeof body.source === "string" ? body.source : "app",
|
|
760
|
-
kind: typeof body.kind === "string" ? body.kind : "fact",
|
|
761
|
-
content: body.content,
|
|
762
|
-
metadata: body.metadata ?? {},
|
|
763
|
-
createdAt: now()
|
|
1262
|
+
async function handleShowDisplay(request) {
|
|
1263
|
+
const body = await readJsonBody(request);
|
|
1264
|
+
const captured = {
|
|
1265
|
+
action: "show",
|
|
1266
|
+
alias: body.alias ? String(body.alias) : void 0,
|
|
1267
|
+
peripheralId: body.peripheralId ? String(body.peripheralId) : void 0,
|
|
1268
|
+
url: body.url ? String(body.url) : void 0
|
|
764
1269
|
};
|
|
765
|
-
state.
|
|
766
|
-
return success({
|
|
1270
|
+
state.edgeDisplays.push(captured);
|
|
1271
|
+
return success({
|
|
1272
|
+
configVersion: nextConfigVersion(),
|
|
1273
|
+
pushed: true,
|
|
1274
|
+
state: "showing"
|
|
1275
|
+
});
|
|
767
1276
|
}
|
|
768
|
-
function
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
const
|
|
772
|
-
|
|
773
|
-
|
|
1277
|
+
function handleClearDisplay(request) {
|
|
1278
|
+
const url = new URL(request.url);
|
|
1279
|
+
const alias = url.searchParams.get("alias");
|
|
1280
|
+
const peripheralId = url.searchParams.get("peripheralId");
|
|
1281
|
+
const captured = {
|
|
1282
|
+
action: "clear",
|
|
1283
|
+
alias: alias ?? void 0,
|
|
1284
|
+
peripheralId: peripheralId ?? void 0
|
|
1285
|
+
};
|
|
1286
|
+
state.edgeDisplays.push(captured);
|
|
1287
|
+
return success({
|
|
1288
|
+
configVersion: nextConfigVersion(),
|
|
1289
|
+
pushed: true,
|
|
1290
|
+
state: "cleared"
|
|
1291
|
+
});
|
|
774
1292
|
}
|
|
775
|
-
async function
|
|
1293
|
+
async function handleTestPrint(request) {
|
|
1294
|
+
const body = await readJsonBody(request);
|
|
1295
|
+
const captured = {
|
|
1296
|
+
alias: body.alias ? String(body.alias) : void 0,
|
|
1297
|
+
deviceId: body.deviceId ? String(body.deviceId) : void 0,
|
|
1298
|
+
peripheralId: body.peripheralId ? String(body.peripheralId) : void 0
|
|
1299
|
+
};
|
|
1300
|
+
state.edgeTestPrints.push(captured);
|
|
1301
|
+
return success({
|
|
1302
|
+
status: "ok",
|
|
1303
|
+
peripheralId: captured.peripheralId ?? captured.alias ?? "default"
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
async function handleEdgeRequest(request, subPath) {
|
|
776
1307
|
const method = request.method;
|
|
777
|
-
if (subPath === ""
|
|
778
|
-
|
|
779
|
-
if (method === "POST") return handleCreate(request);
|
|
1308
|
+
if (subPath === "/print" && method === "POST") {
|
|
1309
|
+
return handlePrint(request);
|
|
780
1310
|
}
|
|
781
|
-
if (subPath === "/
|
|
782
|
-
return
|
|
1311
|
+
if (subPath === "/devices" && method === "GET") {
|
|
1312
|
+
return handleListDevices();
|
|
783
1313
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
return handleAttachLink(linksMatch[1], request);
|
|
1314
|
+
if (subPath === "/peripherals" && method === "GET") {
|
|
1315
|
+
return handleListPeripherals(request);
|
|
787
1316
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
if (method === "GET") return handleListMemory(memoryMatch[1], request);
|
|
791
|
-
if (method === "POST") return handleWriteMemory(memoryMatch[1], request);
|
|
1317
|
+
if (subPath === "/bindings" && method === "GET") {
|
|
1318
|
+
return handleListBindings();
|
|
792
1319
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
if (method === "GET") return handleGet(singleMatch[1]);
|
|
796
|
-
if (method === "PATCH") return handleUpdate(singleMatch[1], request);
|
|
1320
|
+
if (subPath === "/bindings" && method === "PUT") {
|
|
1321
|
+
return handlePair(request);
|
|
797
1322
|
}
|
|
798
|
-
|
|
1323
|
+
const bindingMatch = subPath.match(/^\/bindings\/([^/]+)$/);
|
|
1324
|
+
if (bindingMatch) {
|
|
1325
|
+
const alias = decodeURIComponent(bindingMatch[1]);
|
|
1326
|
+
if (method === "GET") return handleGetBinding(alias);
|
|
1327
|
+
if (method === "DELETE") return handleUnpair(alias);
|
|
1328
|
+
}
|
|
1329
|
+
if (subPath === "/display" && method === "POST") {
|
|
1330
|
+
return handleShowDisplay(request);
|
|
1331
|
+
}
|
|
1332
|
+
if (subPath === "/display" && method === "DELETE") {
|
|
1333
|
+
return handleClearDisplay(request);
|
|
1334
|
+
}
|
|
1335
|
+
if (subPath === "/test-print" && method === "POST") {
|
|
1336
|
+
return handleTestPrint(request);
|
|
1337
|
+
}
|
|
1338
|
+
return edgeFailure(`No edge simulator for ${method} .../edge${subPath}`, 404);
|
|
799
1339
|
}
|
|
800
1340
|
|
|
801
1341
|
// src/simulator/router.ts
|
|
@@ -805,6 +1345,22 @@ var fetchHolder = globalSingleton("fetch-holder", () => ({
|
|
|
805
1345
|
function isLocalHost(hostname) {
|
|
806
1346
|
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "0.0.0.0";
|
|
807
1347
|
}
|
|
1348
|
+
function requiresDeploymentHmac(request, url) {
|
|
1349
|
+
const isStorageHost = url.hostname === STORAGE_TEST_HOST;
|
|
1350
|
+
const isPresignedPut = isStorageHost && request.method === "PUT" && /^\/presigned\/[^/]+$/.test(url.pathname);
|
|
1351
|
+
if (isStorageHost) {
|
|
1352
|
+
return !isPresignedPut;
|
|
1353
|
+
}
|
|
1354
|
+
const dataStoreMatch = url.pathname.match(/^\/api\/data-stores\/[^/]+(\/.*)?$/);
|
|
1355
|
+
const isEmail = url.pathname === "/api/email/send";
|
|
1356
|
+
const identitiesMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/identities(\/.*)?$/);
|
|
1357
|
+
const storeMatch = url.pathname.match(/^\/api\/store\//);
|
|
1358
|
+
const messagingMatch = url.pathname.match(
|
|
1359
|
+
/^\/api\/deployments\/[^/]+\/integrations\/(slack|line|facebook)(\/.*)?$/
|
|
1360
|
+
);
|
|
1361
|
+
const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
|
|
1362
|
+
return !!(dataStoreMatch || isEmail || identitiesMatch || storeMatch || messagingMatch || edgeMatch);
|
|
1363
|
+
}
|
|
808
1364
|
async function handleSimulatedRequest(request, url) {
|
|
809
1365
|
if (url.pathname === "/sql") {
|
|
810
1366
|
return handleNeonSql(requireDb(), request);
|
|
@@ -816,7 +1372,13 @@ async function handleSimulatedRequest(request, url) {
|
|
|
816
1372
|
const dataStoreMatch = url.pathname.match(/^\/api\/data-stores\/[^/]+(\/.*)?$/);
|
|
817
1373
|
const isEmail = url.pathname === "/api/email/send";
|
|
818
1374
|
const identitiesMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/identities(\/.*)?$/);
|
|
819
|
-
|
|
1375
|
+
const storeMatch = url.pathname.match(/^\/api\/store\//);
|
|
1376
|
+
const messagingMatch = url.pathname.match(
|
|
1377
|
+
/^\/api\/deployments\/[^/]+\/integrations\/(slack|line|facebook)(\/.*)?$/
|
|
1378
|
+
);
|
|
1379
|
+
const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
|
|
1380
|
+
const isStorageHost = url.hostname === STORAGE_TEST_HOST;
|
|
1381
|
+
if (requiresDeploymentHmac(request, url)) {
|
|
820
1382
|
const authHeader = request.headers.get("X-Stardeck-Auth");
|
|
821
1383
|
if (!authHeader) {
|
|
822
1384
|
return failure("Missing authentication header", 401);
|
|
@@ -826,34 +1388,47 @@ async function handleSimulatedRequest(request, url) {
|
|
|
826
1388
|
return failure("Invalid authentication", 401);
|
|
827
1389
|
}
|
|
828
1390
|
}
|
|
1391
|
+
if (isStorageHost) {
|
|
1392
|
+
return handleStorageRequest(request, url);
|
|
1393
|
+
}
|
|
829
1394
|
if (isEmail && request.method === "POST") {
|
|
830
1395
|
return handleEmailSend(request);
|
|
831
1396
|
}
|
|
832
1397
|
if (identitiesMatch) {
|
|
833
1398
|
return handleIdentitiesRequest(request, identitiesMatch[1] ?? "");
|
|
834
1399
|
}
|
|
1400
|
+
if (storeMatch) {
|
|
1401
|
+
return handlePaymentsRequest(request, url);
|
|
1402
|
+
}
|
|
1403
|
+
if (messagingMatch) {
|
|
1404
|
+
const channel = messagingMatch[1];
|
|
1405
|
+
return handleMessagingRequest(request, channel, messagingMatch[2] ?? "");
|
|
1406
|
+
}
|
|
1407
|
+
if (edgeMatch) {
|
|
1408
|
+
return handleEdgeRequest(request, edgeMatch[1] ?? "");
|
|
1409
|
+
}
|
|
835
1410
|
if (dataStoreMatch) {
|
|
836
1411
|
const subPath = dataStoreMatch[1] ?? "";
|
|
837
1412
|
const db = requireDb();
|
|
838
|
-
const
|
|
1413
|
+
const readBody = async () => await request.json();
|
|
839
1414
|
if (subPath === "/query" && request.method === "POST") {
|
|
840
|
-
return handleQuery(db, await
|
|
1415
|
+
return handleQuery(db, await readBody());
|
|
841
1416
|
}
|
|
842
1417
|
if (subPath === "/mutate" && request.method === "POST") {
|
|
843
|
-
return handleMutate(db, await
|
|
1418
|
+
return handleMutate(db, await readBody());
|
|
844
1419
|
}
|
|
845
1420
|
if (subPath === "/schema" && request.method === "GET") {
|
|
846
1421
|
return handleGetSchema(db);
|
|
847
1422
|
}
|
|
848
1423
|
if (subPath === "/schema/tables" && request.method === "POST") {
|
|
849
|
-
return handleCreateTable(db, await
|
|
1424
|
+
return handleCreateTable(db, await readBody());
|
|
850
1425
|
}
|
|
851
1426
|
if (subPath === "/schema/columns" && request.method === "POST") {
|
|
852
|
-
return handleAddColumn(db, await
|
|
1427
|
+
return handleAddColumn(db, await readBody());
|
|
853
1428
|
}
|
|
854
1429
|
}
|
|
855
1430
|
return failure(
|
|
856
|
-
`[stardeck-testing] No simulator for ${request.method} ${url.pathname}. Supported: data-store query/mutate/schema, email send, identities CRUD, auth verify/refresh, Neon /sql.`,
|
|
1431
|
+
`[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, auth verify/refresh, Neon /sql.`,
|
|
857
1432
|
404
|
|
858
1433
|
);
|
|
859
1434
|
}
|