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