@stardeck-customer-apps/testing 0.6.2 → 0.7.1
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 +28 -3
- package/dist/index.d.mts +69 -7
- package/dist/index.d.ts +69 -7
- package/dist/index.js +90 -11
- package/dist/index.mjs +90 -11
- package/dist/next/headers-shim.js +2 -0
- package/dist/next/headers-shim.mjs +2 -0
- package/dist/setup.js +80 -11
- package/dist/setup.mjs +80 -11
- package/package.json +4 -4
package/SKILL.md
CHANGED
|
@@ -175,7 +175,8 @@ externalId })` helper models a trusted platform/channel link, and
|
|
|
175
175
|
`.all()`, `.latest()`, `.to(recipient)`, `.channel("line")`, `.count`, `.clear()`.
|
|
176
176
|
- `app.edge` — edge print/display/bindings from edge-sdk: `.prints`, `.displays`,
|
|
177
177
|
`.testPrints`, `.latestPrint()`, `.latestDisplay()`, `.bindings`,
|
|
178
|
-
`.seedDevices()`, `.seedPeripherals()`, `.seedBindings()`,
|
|
178
|
+
`.seedDevices()`, `.seedPeripherals()`, `.seedBindings()`,
|
|
179
|
+
`.seedCapabilities(caps, peripheralId?)`, `.count`, `.clear()`.
|
|
179
180
|
- `app.query(sql, params?)` / `app.db` — direct database access for
|
|
180
181
|
assertions and seeding.
|
|
181
182
|
- `callRoute(handler, opts)` — invoke an App Router route handler with a real
|
|
@@ -242,8 +243,32 @@ cursor, limit })`; `list()` intentionally keeps its previous array shape.
|
|
|
242
243
|
- `client.slack` / `client.line` / `client.facebook` send endpoints — captured
|
|
243
244
|
in `app.messages`.
|
|
244
245
|
- `createEdgeClient()` (print, pair/unpair, list devices/peripherals, show/clear
|
|
245
|
-
display, test print) — captured in `app.edge`; seed
|
|
246
|
-
pairing pickers via `app.edge.seedDevices()` /
|
|
246
|
+
display, test print, `getCapabilities()`) — captured in `app.edge`; seed
|
|
247
|
+
devices/peripherals for pairing pickers via `app.edge.seedDevices()` /
|
|
248
|
+
`seedPeripherals()`.
|
|
249
|
+
|
|
250
|
+
### Receipt layout and printer capabilities
|
|
251
|
+
|
|
252
|
+
`edge.getCapabilities(target)` answers with a stock 80mm text-mode printer
|
|
253
|
+
unless the test says otherwise. Seed a different one to exercise layout,
|
|
254
|
+
degradation, and preview code — per peripheral when a test models more than one
|
|
255
|
+
station:
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
app.edge.seedPeripherals([kitchenPrinter, counterPrinter]);
|
|
259
|
+
app.edge.seedCapabilities({ ...caps58mm, renderMode: "raster" }, kitchenPrinter.id);
|
|
260
|
+
|
|
261
|
+
const caps = await edge.getCapabilities({ peripheralId: kitchenPrinter.id });
|
|
262
|
+
expect(caps.columns).toBe(32);
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
The simulator resolves the target the way production does: an unpaired alias
|
|
266
|
+
throws `BindingError` (`BINDING_NOT_FOUND`) and an unknown peripheral 404s, so
|
|
267
|
+
target-resolution bugs surface in tests rather than in the field.
|
|
268
|
+
|
|
269
|
+
Printed receipts are captured whole, so assert on the ordered blocks an app
|
|
270
|
+
produced — `app.edge.latestPrint()?.receipt.blocks` — not just its totals.
|
|
271
|
+
|
|
247
272
|
- `next/headers` (`headers()`/`cookies()`) inside handlers under `callRoute`.
|
|
248
273
|
|
|
249
274
|
## End-to-end (Playwright) suite
|
package/dist/index.d.mts
CHANGED
|
@@ -184,6 +184,10 @@ interface CapturedMessage {
|
|
|
184
184
|
threadTs?: string;
|
|
185
185
|
messagingType?: string;
|
|
186
186
|
tag?: string;
|
|
187
|
+
/** LINE image push — HTTPS URL of the full-size image. */
|
|
188
|
+
originalContentUrl?: string;
|
|
189
|
+
/** LINE image push — HTTPS URL of the preview; may be omitted when not sent. */
|
|
190
|
+
previewImageUrl?: string;
|
|
187
191
|
};
|
|
188
192
|
connectionId?: string;
|
|
189
193
|
sentAt: Date;
|
|
@@ -200,17 +204,51 @@ interface TestMessages {
|
|
|
200
204
|
clear(): void;
|
|
201
205
|
get count(): number;
|
|
202
206
|
}
|
|
207
|
+
interface ReceiptItem {
|
|
208
|
+
name: string;
|
|
209
|
+
quantity: number;
|
|
210
|
+
unitPrice: number;
|
|
211
|
+
total: number;
|
|
212
|
+
note?: string;
|
|
213
|
+
}
|
|
214
|
+
type ReceiptAlign = "left" | "center" | "right";
|
|
215
|
+
type ReceiptTextSize = "small" | "normal" | "large";
|
|
216
|
+
/** Mirrors the edge SDK's block model so assertions can inspect the exact
|
|
217
|
+
* ordered content an app printed, not just its totals. */
|
|
218
|
+
type ReceiptBlock = {
|
|
219
|
+
type: "text";
|
|
220
|
+
text: string;
|
|
221
|
+
align?: ReceiptAlign;
|
|
222
|
+
bold?: boolean;
|
|
223
|
+
size?: ReceiptTextSize;
|
|
224
|
+
} | {
|
|
225
|
+
type: "columns";
|
|
226
|
+
left: string;
|
|
227
|
+
right: string;
|
|
228
|
+
bold?: boolean;
|
|
229
|
+
size?: ReceiptTextSize;
|
|
230
|
+
} | {
|
|
231
|
+
type: "separator";
|
|
232
|
+
style?: "dashed" | "solid";
|
|
233
|
+
} | {
|
|
234
|
+
type: "spacer";
|
|
235
|
+
lines?: number;
|
|
236
|
+
} | {
|
|
237
|
+
type: "items";
|
|
238
|
+
rows: ReceiptItem[];
|
|
239
|
+
} | {
|
|
240
|
+
type: "barcode";
|
|
241
|
+
format: "qr" | "code128";
|
|
242
|
+
data: string;
|
|
243
|
+
align?: ReceiptAlign;
|
|
244
|
+
};
|
|
203
245
|
interface ReceiptPayload {
|
|
246
|
+
paperWidth?: "58mm" | "80mm";
|
|
247
|
+
blocks?: ReceiptBlock[];
|
|
204
248
|
header?: {
|
|
205
249
|
lines: string[];
|
|
206
250
|
};
|
|
207
|
-
items?:
|
|
208
|
-
name: string;
|
|
209
|
-
quantity: number;
|
|
210
|
-
unitPrice: number;
|
|
211
|
-
total: number;
|
|
212
|
-
note?: string;
|
|
213
|
-
}>;
|
|
251
|
+
items?: ReceiptItem[];
|
|
214
252
|
subtotal?: number;
|
|
215
253
|
tax?: number;
|
|
216
254
|
discount?: number;
|
|
@@ -228,6 +266,23 @@ interface ReceiptPayload {
|
|
|
228
266
|
};
|
|
229
267
|
rawEscPos?: string;
|
|
230
268
|
}
|
|
269
|
+
/** Mirrors the edge SDK's PrinterCapabilities so tests can drive
|
|
270
|
+
* `edge.getCapabilities()` without the SDK being a dependency here. */
|
|
271
|
+
interface PrinterCapabilities {
|
|
272
|
+
paperWidth: "58mm" | "80mm";
|
|
273
|
+
columns: number;
|
|
274
|
+
dotsPerLine: number;
|
|
275
|
+
renderMode: "text" | "raster";
|
|
276
|
+
textEncoding: "utf8" | "tis620";
|
|
277
|
+
unicodeText: boolean;
|
|
278
|
+
thaiText: boolean;
|
|
279
|
+
bold: boolean;
|
|
280
|
+
textSizes: ReceiptTextSize[];
|
|
281
|
+
barcodeFormats: Array<"qr" | "code128">;
|
|
282
|
+
rasterImages: boolean;
|
|
283
|
+
cut: boolean;
|
|
284
|
+
cashDrawer: boolean;
|
|
285
|
+
}
|
|
231
286
|
interface DeviceInfo {
|
|
232
287
|
id: string;
|
|
233
288
|
displayName: string;
|
|
@@ -297,6 +352,13 @@ interface TestEdge {
|
|
|
297
352
|
seedDevices(devices: DeviceInfo[]): void;
|
|
298
353
|
seedPeripherals(peripherals: PeripheralInfo[]): void;
|
|
299
354
|
seedBindings(bindings: BindingInfo[]): void;
|
|
355
|
+
/**
|
|
356
|
+
* Answer `edge.getCapabilities()` with a specific printer — e.g. a 58mm or a
|
|
357
|
+
* raster/Thai one — so layout and preview code can be tested against it.
|
|
358
|
+
* Pass a `peripheralId` to model stations with different printers; without
|
|
359
|
+
* one this sets the default every untargeted lookup falls back to.
|
|
360
|
+
*/
|
|
361
|
+
seedCapabilities(capabilities: PrinterCapabilities | null, peripheralId?: string): void;
|
|
300
362
|
clear(): void;
|
|
301
363
|
get count(): number;
|
|
302
364
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -184,6 +184,10 @@ interface CapturedMessage {
|
|
|
184
184
|
threadTs?: string;
|
|
185
185
|
messagingType?: string;
|
|
186
186
|
tag?: string;
|
|
187
|
+
/** LINE image push — HTTPS URL of the full-size image. */
|
|
188
|
+
originalContentUrl?: string;
|
|
189
|
+
/** LINE image push — HTTPS URL of the preview; may be omitted when not sent. */
|
|
190
|
+
previewImageUrl?: string;
|
|
187
191
|
};
|
|
188
192
|
connectionId?: string;
|
|
189
193
|
sentAt: Date;
|
|
@@ -200,17 +204,51 @@ interface TestMessages {
|
|
|
200
204
|
clear(): void;
|
|
201
205
|
get count(): number;
|
|
202
206
|
}
|
|
207
|
+
interface ReceiptItem {
|
|
208
|
+
name: string;
|
|
209
|
+
quantity: number;
|
|
210
|
+
unitPrice: number;
|
|
211
|
+
total: number;
|
|
212
|
+
note?: string;
|
|
213
|
+
}
|
|
214
|
+
type ReceiptAlign = "left" | "center" | "right";
|
|
215
|
+
type ReceiptTextSize = "small" | "normal" | "large";
|
|
216
|
+
/** Mirrors the edge SDK's block model so assertions can inspect the exact
|
|
217
|
+
* ordered content an app printed, not just its totals. */
|
|
218
|
+
type ReceiptBlock = {
|
|
219
|
+
type: "text";
|
|
220
|
+
text: string;
|
|
221
|
+
align?: ReceiptAlign;
|
|
222
|
+
bold?: boolean;
|
|
223
|
+
size?: ReceiptTextSize;
|
|
224
|
+
} | {
|
|
225
|
+
type: "columns";
|
|
226
|
+
left: string;
|
|
227
|
+
right: string;
|
|
228
|
+
bold?: boolean;
|
|
229
|
+
size?: ReceiptTextSize;
|
|
230
|
+
} | {
|
|
231
|
+
type: "separator";
|
|
232
|
+
style?: "dashed" | "solid";
|
|
233
|
+
} | {
|
|
234
|
+
type: "spacer";
|
|
235
|
+
lines?: number;
|
|
236
|
+
} | {
|
|
237
|
+
type: "items";
|
|
238
|
+
rows: ReceiptItem[];
|
|
239
|
+
} | {
|
|
240
|
+
type: "barcode";
|
|
241
|
+
format: "qr" | "code128";
|
|
242
|
+
data: string;
|
|
243
|
+
align?: ReceiptAlign;
|
|
244
|
+
};
|
|
203
245
|
interface ReceiptPayload {
|
|
246
|
+
paperWidth?: "58mm" | "80mm";
|
|
247
|
+
blocks?: ReceiptBlock[];
|
|
204
248
|
header?: {
|
|
205
249
|
lines: string[];
|
|
206
250
|
};
|
|
207
|
-
items?:
|
|
208
|
-
name: string;
|
|
209
|
-
quantity: number;
|
|
210
|
-
unitPrice: number;
|
|
211
|
-
total: number;
|
|
212
|
-
note?: string;
|
|
213
|
-
}>;
|
|
251
|
+
items?: ReceiptItem[];
|
|
214
252
|
subtotal?: number;
|
|
215
253
|
tax?: number;
|
|
216
254
|
discount?: number;
|
|
@@ -228,6 +266,23 @@ interface ReceiptPayload {
|
|
|
228
266
|
};
|
|
229
267
|
rawEscPos?: string;
|
|
230
268
|
}
|
|
269
|
+
/** Mirrors the edge SDK's PrinterCapabilities so tests can drive
|
|
270
|
+
* `edge.getCapabilities()` without the SDK being a dependency here. */
|
|
271
|
+
interface PrinterCapabilities {
|
|
272
|
+
paperWidth: "58mm" | "80mm";
|
|
273
|
+
columns: number;
|
|
274
|
+
dotsPerLine: number;
|
|
275
|
+
renderMode: "text" | "raster";
|
|
276
|
+
textEncoding: "utf8" | "tis620";
|
|
277
|
+
unicodeText: boolean;
|
|
278
|
+
thaiText: boolean;
|
|
279
|
+
bold: boolean;
|
|
280
|
+
textSizes: ReceiptTextSize[];
|
|
281
|
+
barcodeFormats: Array<"qr" | "code128">;
|
|
282
|
+
rasterImages: boolean;
|
|
283
|
+
cut: boolean;
|
|
284
|
+
cashDrawer: boolean;
|
|
285
|
+
}
|
|
231
286
|
interface DeviceInfo {
|
|
232
287
|
id: string;
|
|
233
288
|
displayName: string;
|
|
@@ -297,6 +352,13 @@ interface TestEdge {
|
|
|
297
352
|
seedDevices(devices: DeviceInfo[]): void;
|
|
298
353
|
seedPeripherals(peripherals: PeripheralInfo[]): void;
|
|
299
354
|
seedBindings(bindings: BindingInfo[]): void;
|
|
355
|
+
/**
|
|
356
|
+
* Answer `edge.getCapabilities()` with a specific printer — e.g. a 58mm or a
|
|
357
|
+
* raster/Thai one — so layout and preview code can be tested against it.
|
|
358
|
+
* Pass a `peripheralId` to model stations with different printers; without
|
|
359
|
+
* one this sets the default every untargeted lookup falls back to.
|
|
360
|
+
*/
|
|
361
|
+
seedCapabilities(capabilities: PrinterCapabilities | null, peripheralId?: string): void;
|
|
300
362
|
clear(): void;
|
|
301
363
|
get count(): number;
|
|
302
364
|
}
|
package/dist/index.js
CHANGED
|
@@ -117,6 +117,8 @@ var state = globalSingleton("state", () => ({
|
|
|
117
117
|
edgeDevices: [],
|
|
118
118
|
edgePeripherals: [],
|
|
119
119
|
edgeBindings: /* @__PURE__ */ new Map(),
|
|
120
|
+
edgeCapabilities: null,
|
|
121
|
+
edgePeripheralCapabilities: /* @__PURE__ */ new Map(),
|
|
120
122
|
allowNetwork: false
|
|
121
123
|
}));
|
|
122
124
|
function requireDb() {
|
|
@@ -1695,6 +1697,15 @@ function createStorage() {
|
|
|
1695
1697
|
|
|
1696
1698
|
// src/simulator/messaging.ts
|
|
1697
1699
|
var import_node_crypto7 = __toESM(require("crypto"));
|
|
1700
|
+
function isValidLineImageUrl(url) {
|
|
1701
|
+
if (url.length > 2e3 || !url.startsWith("https://")) return false;
|
|
1702
|
+
try {
|
|
1703
|
+
new URL(url);
|
|
1704
|
+
return true;
|
|
1705
|
+
} catch {
|
|
1706
|
+
return false;
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1698
1709
|
function captureMessage(channel, recipient, body, connectionId) {
|
|
1699
1710
|
const message = {
|
|
1700
1711
|
channel,
|
|
@@ -1704,7 +1715,9 @@ function captureMessage(channel, recipient, body, connectionId) {
|
|
|
1704
1715
|
blocks: body.blocks,
|
|
1705
1716
|
threadTs: body.threadTs ? String(body.threadTs) : void 0,
|
|
1706
1717
|
messagingType: body.messagingType ? String(body.messagingType) : void 0,
|
|
1707
|
-
tag: body.tag ? String(body.tag) : void 0
|
|
1718
|
+
tag: body.tag ? String(body.tag) : void 0,
|
|
1719
|
+
originalContentUrl: body.originalContentUrl ? String(body.originalContentUrl) : void 0,
|
|
1720
|
+
previewImageUrl: body.previewImageUrl ? String(body.previewImageUrl) : void 0
|
|
1708
1721
|
},
|
|
1709
1722
|
connectionId,
|
|
1710
1723
|
sentAt: /* @__PURE__ */ new Date()
|
|
@@ -1742,18 +1755,33 @@ async function handleMessagingRequest(request, channel, subPath) {
|
|
|
1742
1755
|
const userId = String(body.userId ?? "");
|
|
1743
1756
|
const message = body.message;
|
|
1744
1757
|
if (!userId) return failure("userId is required");
|
|
1745
|
-
if (!message || message.type !== "text"
|
|
1746
|
-
return failure("message must be { type: 'text',
|
|
1758
|
+
if (!message || message.type !== "text" && message.type !== "image") {
|
|
1759
|
+
return failure("message must be { type: 'text' | 'image', ... }");
|
|
1747
1760
|
}
|
|
1748
|
-
|
|
1749
|
-
|
|
1761
|
+
const connectionId = body.connectionId ? String(body.connectionId) : void 0;
|
|
1762
|
+
if (message.type === "text") {
|
|
1763
|
+
if (!message.text) {
|
|
1764
|
+
return failure("message must be { type: 'text', text: string }");
|
|
1765
|
+
}
|
|
1766
|
+
if (message.text.length > 5e3) {
|
|
1767
|
+
return failure("message text exceeds maximum length");
|
|
1768
|
+
}
|
|
1769
|
+
captureMessage("line", userId, { text: message.text }, connectionId);
|
|
1770
|
+
} else {
|
|
1771
|
+
const originalContentUrl = message.originalContentUrl;
|
|
1772
|
+
if (!originalContentUrl || !isValidLineImageUrl(originalContentUrl)) {
|
|
1773
|
+
return failure("originalContentUrl must be a valid HTTPS URL (max 2000 chars)");
|
|
1774
|
+
}
|
|
1775
|
+
if (message.previewImageUrl !== void 0 && !isValidLineImageUrl(message.previewImageUrl)) {
|
|
1776
|
+
return failure("previewImageUrl must be a valid HTTPS URL (max 2000 chars)");
|
|
1777
|
+
}
|
|
1778
|
+
captureMessage(
|
|
1779
|
+
"line",
|
|
1780
|
+
userId,
|
|
1781
|
+
{ originalContentUrl, previewImageUrl: message.previewImageUrl ?? originalContentUrl },
|
|
1782
|
+
connectionId
|
|
1783
|
+
);
|
|
1750
1784
|
}
|
|
1751
|
-
captureMessage(
|
|
1752
|
-
"line",
|
|
1753
|
-
userId,
|
|
1754
|
-
{ text: message.text },
|
|
1755
|
-
body.connectionId ? String(body.connectionId) : void 0
|
|
1756
|
-
);
|
|
1757
1785
|
return success({ success: true });
|
|
1758
1786
|
}
|
|
1759
1787
|
if (channel === "facebook" && subPath === "/send" && method === "POST") {
|
|
@@ -1851,6 +1879,44 @@ function buildBinding(alias, peripheralId) {
|
|
|
1851
1879
|
updatedAt: now2()
|
|
1852
1880
|
};
|
|
1853
1881
|
}
|
|
1882
|
+
var DEFAULT_CAPABILITIES = {
|
|
1883
|
+
paperWidth: "80mm",
|
|
1884
|
+
columns: 48,
|
|
1885
|
+
dotsPerLine: 576,
|
|
1886
|
+
renderMode: "text",
|
|
1887
|
+
textEncoding: "utf8",
|
|
1888
|
+
unicodeText: false,
|
|
1889
|
+
thaiText: false,
|
|
1890
|
+
bold: true,
|
|
1891
|
+
textSizes: ["small", "normal", "large"],
|
|
1892
|
+
barcodeFormats: ["qr"],
|
|
1893
|
+
rasterImages: true,
|
|
1894
|
+
cut: true,
|
|
1895
|
+
cashDrawer: false
|
|
1896
|
+
};
|
|
1897
|
+
function handleCapabilities(request) {
|
|
1898
|
+
const url = new URL(request.url);
|
|
1899
|
+
const alias = url.searchParams.get("alias");
|
|
1900
|
+
const deviceId = url.searchParams.get("deviceId");
|
|
1901
|
+
let peripheralId = url.searchParams.get("peripheralId");
|
|
1902
|
+
if (alias) {
|
|
1903
|
+
const binding = state.edgeBindings.get(alias);
|
|
1904
|
+
if (!binding) return bindingNotFound(alias);
|
|
1905
|
+
peripheralId = binding.peripheral?.id ?? null;
|
|
1906
|
+
}
|
|
1907
|
+
if (peripheralId) {
|
|
1908
|
+
const peripheral = findPeripheral(peripheralId);
|
|
1909
|
+
if (!peripheral) {
|
|
1910
|
+
return edgeFailure("Peripheral not found or its device is not granted to this project", 404);
|
|
1911
|
+
}
|
|
1912
|
+
const seeded = state.edgePeripheralCapabilities.get(peripheralId);
|
|
1913
|
+
return success({ capabilities: seeded ?? state.edgeCapabilities ?? DEFAULT_CAPABILITIES });
|
|
1914
|
+
}
|
|
1915
|
+
if (deviceId && !state.edgeDevices.some((d) => d.id === deviceId)) {
|
|
1916
|
+
return edgeFailure("Project does not have access to this device", 403);
|
|
1917
|
+
}
|
|
1918
|
+
return success({ capabilities: state.edgeCapabilities ?? DEFAULT_CAPABILITIES });
|
|
1919
|
+
}
|
|
1854
1920
|
function handleListDevices() {
|
|
1855
1921
|
return success({ devices: [...state.edgeDevices] });
|
|
1856
1922
|
}
|
|
@@ -1954,6 +2020,9 @@ async function handleEdgeRequest(request, subPath) {
|
|
|
1954
2020
|
if (subPath === "/print" && method === "POST") {
|
|
1955
2021
|
return handlePrint(request);
|
|
1956
2022
|
}
|
|
2023
|
+
if (subPath === "/capabilities" && method === "GET") {
|
|
2024
|
+
return handleCapabilities(request);
|
|
2025
|
+
}
|
|
1957
2026
|
if (subPath === "/devices" && method === "GET") {
|
|
1958
2027
|
return handleListDevices();
|
|
1959
2028
|
}
|
|
@@ -2015,7 +2084,17 @@ function createEdge() {
|
|
|
2015
2084
|
state.edgeBindings.set(binding.alias, binding);
|
|
2016
2085
|
}
|
|
2017
2086
|
},
|
|
2087
|
+
seedCapabilities(capabilities, peripheralId) {
|
|
2088
|
+
if (peripheralId === void 0) {
|
|
2089
|
+
state.edgeCapabilities = capabilities;
|
|
2090
|
+
return;
|
|
2091
|
+
}
|
|
2092
|
+
if (capabilities === null) state.edgePeripheralCapabilities.delete(peripheralId);
|
|
2093
|
+
else state.edgePeripheralCapabilities.set(peripheralId, capabilities);
|
|
2094
|
+
},
|
|
2018
2095
|
clear() {
|
|
2096
|
+
state.edgeCapabilities = null;
|
|
2097
|
+
state.edgePeripheralCapabilities.clear();
|
|
2019
2098
|
state.edgePrints = [];
|
|
2020
2099
|
state.edgeDisplays = [];
|
|
2021
2100
|
state.edgeTestPrints = [];
|
package/dist/index.mjs
CHANGED
|
@@ -71,6 +71,8 @@ var state = globalSingleton("state", () => ({
|
|
|
71
71
|
edgeDevices: [],
|
|
72
72
|
edgePeripherals: [],
|
|
73
73
|
edgeBindings: /* @__PURE__ */ new Map(),
|
|
74
|
+
edgeCapabilities: null,
|
|
75
|
+
edgePeripheralCapabilities: /* @__PURE__ */ new Map(),
|
|
74
76
|
allowNetwork: false
|
|
75
77
|
}));
|
|
76
78
|
function requireDb() {
|
|
@@ -1649,6 +1651,15 @@ function createStorage() {
|
|
|
1649
1651
|
|
|
1650
1652
|
// src/simulator/messaging.ts
|
|
1651
1653
|
import crypto7 from "crypto";
|
|
1654
|
+
function isValidLineImageUrl(url) {
|
|
1655
|
+
if (url.length > 2e3 || !url.startsWith("https://")) return false;
|
|
1656
|
+
try {
|
|
1657
|
+
new URL(url);
|
|
1658
|
+
return true;
|
|
1659
|
+
} catch {
|
|
1660
|
+
return false;
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1652
1663
|
function captureMessage(channel, recipient, body, connectionId) {
|
|
1653
1664
|
const message = {
|
|
1654
1665
|
channel,
|
|
@@ -1658,7 +1669,9 @@ function captureMessage(channel, recipient, body, connectionId) {
|
|
|
1658
1669
|
blocks: body.blocks,
|
|
1659
1670
|
threadTs: body.threadTs ? String(body.threadTs) : void 0,
|
|
1660
1671
|
messagingType: body.messagingType ? String(body.messagingType) : void 0,
|
|
1661
|
-
tag: body.tag ? String(body.tag) : void 0
|
|
1672
|
+
tag: body.tag ? String(body.tag) : void 0,
|
|
1673
|
+
originalContentUrl: body.originalContentUrl ? String(body.originalContentUrl) : void 0,
|
|
1674
|
+
previewImageUrl: body.previewImageUrl ? String(body.previewImageUrl) : void 0
|
|
1662
1675
|
},
|
|
1663
1676
|
connectionId,
|
|
1664
1677
|
sentAt: /* @__PURE__ */ new Date()
|
|
@@ -1696,18 +1709,33 @@ async function handleMessagingRequest(request, channel, subPath) {
|
|
|
1696
1709
|
const userId = String(body.userId ?? "");
|
|
1697
1710
|
const message = body.message;
|
|
1698
1711
|
if (!userId) return failure("userId is required");
|
|
1699
|
-
if (!message || message.type !== "text"
|
|
1700
|
-
return failure("message must be { type: 'text',
|
|
1712
|
+
if (!message || message.type !== "text" && message.type !== "image") {
|
|
1713
|
+
return failure("message must be { type: 'text' | 'image', ... }");
|
|
1701
1714
|
}
|
|
1702
|
-
|
|
1703
|
-
|
|
1715
|
+
const connectionId = body.connectionId ? String(body.connectionId) : void 0;
|
|
1716
|
+
if (message.type === "text") {
|
|
1717
|
+
if (!message.text) {
|
|
1718
|
+
return failure("message must be { type: 'text', text: string }");
|
|
1719
|
+
}
|
|
1720
|
+
if (message.text.length > 5e3) {
|
|
1721
|
+
return failure("message text exceeds maximum length");
|
|
1722
|
+
}
|
|
1723
|
+
captureMessage("line", userId, { text: message.text }, connectionId);
|
|
1724
|
+
} else {
|
|
1725
|
+
const originalContentUrl = message.originalContentUrl;
|
|
1726
|
+
if (!originalContentUrl || !isValidLineImageUrl(originalContentUrl)) {
|
|
1727
|
+
return failure("originalContentUrl must be a valid HTTPS URL (max 2000 chars)");
|
|
1728
|
+
}
|
|
1729
|
+
if (message.previewImageUrl !== void 0 && !isValidLineImageUrl(message.previewImageUrl)) {
|
|
1730
|
+
return failure("previewImageUrl must be a valid HTTPS URL (max 2000 chars)");
|
|
1731
|
+
}
|
|
1732
|
+
captureMessage(
|
|
1733
|
+
"line",
|
|
1734
|
+
userId,
|
|
1735
|
+
{ originalContentUrl, previewImageUrl: message.previewImageUrl ?? originalContentUrl },
|
|
1736
|
+
connectionId
|
|
1737
|
+
);
|
|
1704
1738
|
}
|
|
1705
|
-
captureMessage(
|
|
1706
|
-
"line",
|
|
1707
|
-
userId,
|
|
1708
|
-
{ text: message.text },
|
|
1709
|
-
body.connectionId ? String(body.connectionId) : void 0
|
|
1710
|
-
);
|
|
1711
1739
|
return success({ success: true });
|
|
1712
1740
|
}
|
|
1713
1741
|
if (channel === "facebook" && subPath === "/send" && method === "POST") {
|
|
@@ -1805,6 +1833,44 @@ function buildBinding(alias, peripheralId) {
|
|
|
1805
1833
|
updatedAt: now2()
|
|
1806
1834
|
};
|
|
1807
1835
|
}
|
|
1836
|
+
var DEFAULT_CAPABILITIES = {
|
|
1837
|
+
paperWidth: "80mm",
|
|
1838
|
+
columns: 48,
|
|
1839
|
+
dotsPerLine: 576,
|
|
1840
|
+
renderMode: "text",
|
|
1841
|
+
textEncoding: "utf8",
|
|
1842
|
+
unicodeText: false,
|
|
1843
|
+
thaiText: false,
|
|
1844
|
+
bold: true,
|
|
1845
|
+
textSizes: ["small", "normal", "large"],
|
|
1846
|
+
barcodeFormats: ["qr"],
|
|
1847
|
+
rasterImages: true,
|
|
1848
|
+
cut: true,
|
|
1849
|
+
cashDrawer: false
|
|
1850
|
+
};
|
|
1851
|
+
function handleCapabilities(request) {
|
|
1852
|
+
const url = new URL(request.url);
|
|
1853
|
+
const alias = url.searchParams.get("alias");
|
|
1854
|
+
const deviceId = url.searchParams.get("deviceId");
|
|
1855
|
+
let peripheralId = url.searchParams.get("peripheralId");
|
|
1856
|
+
if (alias) {
|
|
1857
|
+
const binding = state.edgeBindings.get(alias);
|
|
1858
|
+
if (!binding) return bindingNotFound(alias);
|
|
1859
|
+
peripheralId = binding.peripheral?.id ?? null;
|
|
1860
|
+
}
|
|
1861
|
+
if (peripheralId) {
|
|
1862
|
+
const peripheral = findPeripheral(peripheralId);
|
|
1863
|
+
if (!peripheral) {
|
|
1864
|
+
return edgeFailure("Peripheral not found or its device is not granted to this project", 404);
|
|
1865
|
+
}
|
|
1866
|
+
const seeded = state.edgePeripheralCapabilities.get(peripheralId);
|
|
1867
|
+
return success({ capabilities: seeded ?? state.edgeCapabilities ?? DEFAULT_CAPABILITIES });
|
|
1868
|
+
}
|
|
1869
|
+
if (deviceId && !state.edgeDevices.some((d) => d.id === deviceId)) {
|
|
1870
|
+
return edgeFailure("Project does not have access to this device", 403);
|
|
1871
|
+
}
|
|
1872
|
+
return success({ capabilities: state.edgeCapabilities ?? DEFAULT_CAPABILITIES });
|
|
1873
|
+
}
|
|
1808
1874
|
function handleListDevices() {
|
|
1809
1875
|
return success({ devices: [...state.edgeDevices] });
|
|
1810
1876
|
}
|
|
@@ -1908,6 +1974,9 @@ async function handleEdgeRequest(request, subPath) {
|
|
|
1908
1974
|
if (subPath === "/print" && method === "POST") {
|
|
1909
1975
|
return handlePrint(request);
|
|
1910
1976
|
}
|
|
1977
|
+
if (subPath === "/capabilities" && method === "GET") {
|
|
1978
|
+
return handleCapabilities(request);
|
|
1979
|
+
}
|
|
1911
1980
|
if (subPath === "/devices" && method === "GET") {
|
|
1912
1981
|
return handleListDevices();
|
|
1913
1982
|
}
|
|
@@ -1969,7 +2038,17 @@ function createEdge() {
|
|
|
1969
2038
|
state.edgeBindings.set(binding.alias, binding);
|
|
1970
2039
|
}
|
|
1971
2040
|
},
|
|
2041
|
+
seedCapabilities(capabilities, peripheralId) {
|
|
2042
|
+
if (peripheralId === void 0) {
|
|
2043
|
+
state.edgeCapabilities = capabilities;
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
2046
|
+
if (capabilities === null) state.edgePeripheralCapabilities.delete(peripheralId);
|
|
2047
|
+
else state.edgePeripheralCapabilities.set(peripheralId, capabilities);
|
|
2048
|
+
},
|
|
1972
2049
|
clear() {
|
|
2050
|
+
state.edgeCapabilities = null;
|
|
2051
|
+
state.edgePeripheralCapabilities.clear();
|
|
1973
2052
|
state.edgePrints = [];
|
|
1974
2053
|
state.edgeDisplays = [];
|
|
1975
2054
|
state.edgeTestPrints = [];
|
package/dist/setup.js
CHANGED
|
@@ -57,6 +57,8 @@ var state = globalSingleton("state", () => ({
|
|
|
57
57
|
edgeDevices: [],
|
|
58
58
|
edgePeripherals: [],
|
|
59
59
|
edgeBindings: /* @__PURE__ */ new Map(),
|
|
60
|
+
edgeCapabilities: null,
|
|
61
|
+
edgePeripheralCapabilities: /* @__PURE__ */ new Map(),
|
|
60
62
|
allowNetwork: false
|
|
61
63
|
}));
|
|
62
64
|
function requireDb() {
|
|
@@ -1321,6 +1323,15 @@ async function handleStorageRequest(request, url) {
|
|
|
1321
1323
|
|
|
1322
1324
|
// src/simulator/messaging.ts
|
|
1323
1325
|
var import_node_crypto7 = __toESM(require("crypto"));
|
|
1326
|
+
function isValidLineImageUrl(url) {
|
|
1327
|
+
if (url.length > 2e3 || !url.startsWith("https://")) return false;
|
|
1328
|
+
try {
|
|
1329
|
+
new URL(url);
|
|
1330
|
+
return true;
|
|
1331
|
+
} catch {
|
|
1332
|
+
return false;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1324
1335
|
function captureMessage(channel, recipient, body, connectionId) {
|
|
1325
1336
|
const message = {
|
|
1326
1337
|
channel,
|
|
@@ -1330,7 +1341,9 @@ function captureMessage(channel, recipient, body, connectionId) {
|
|
|
1330
1341
|
blocks: body.blocks,
|
|
1331
1342
|
threadTs: body.threadTs ? String(body.threadTs) : void 0,
|
|
1332
1343
|
messagingType: body.messagingType ? String(body.messagingType) : void 0,
|
|
1333
|
-
tag: body.tag ? String(body.tag) : void 0
|
|
1344
|
+
tag: body.tag ? String(body.tag) : void 0,
|
|
1345
|
+
originalContentUrl: body.originalContentUrl ? String(body.originalContentUrl) : void 0,
|
|
1346
|
+
previewImageUrl: body.previewImageUrl ? String(body.previewImageUrl) : void 0
|
|
1334
1347
|
},
|
|
1335
1348
|
connectionId,
|
|
1336
1349
|
sentAt: /* @__PURE__ */ new Date()
|
|
@@ -1368,18 +1381,33 @@ async function handleMessagingRequest(request, channel, subPath) {
|
|
|
1368
1381
|
const userId = String(body.userId ?? "");
|
|
1369
1382
|
const message = body.message;
|
|
1370
1383
|
if (!userId) return failure("userId is required");
|
|
1371
|
-
if (!message || message.type !== "text"
|
|
1372
|
-
return failure("message must be { type: 'text',
|
|
1384
|
+
if (!message || message.type !== "text" && message.type !== "image") {
|
|
1385
|
+
return failure("message must be { type: 'text' | 'image', ... }");
|
|
1373
1386
|
}
|
|
1374
|
-
|
|
1375
|
-
|
|
1387
|
+
const connectionId = body.connectionId ? String(body.connectionId) : void 0;
|
|
1388
|
+
if (message.type === "text") {
|
|
1389
|
+
if (!message.text) {
|
|
1390
|
+
return failure("message must be { type: 'text', text: string }");
|
|
1391
|
+
}
|
|
1392
|
+
if (message.text.length > 5e3) {
|
|
1393
|
+
return failure("message text exceeds maximum length");
|
|
1394
|
+
}
|
|
1395
|
+
captureMessage("line", userId, { text: message.text }, connectionId);
|
|
1396
|
+
} else {
|
|
1397
|
+
const originalContentUrl = message.originalContentUrl;
|
|
1398
|
+
if (!originalContentUrl || !isValidLineImageUrl(originalContentUrl)) {
|
|
1399
|
+
return failure("originalContentUrl must be a valid HTTPS URL (max 2000 chars)");
|
|
1400
|
+
}
|
|
1401
|
+
if (message.previewImageUrl !== void 0 && !isValidLineImageUrl(message.previewImageUrl)) {
|
|
1402
|
+
return failure("previewImageUrl must be a valid HTTPS URL (max 2000 chars)");
|
|
1403
|
+
}
|
|
1404
|
+
captureMessage(
|
|
1405
|
+
"line",
|
|
1406
|
+
userId,
|
|
1407
|
+
{ originalContentUrl, previewImageUrl: message.previewImageUrl ?? originalContentUrl },
|
|
1408
|
+
connectionId
|
|
1409
|
+
);
|
|
1376
1410
|
}
|
|
1377
|
-
captureMessage(
|
|
1378
|
-
"line",
|
|
1379
|
-
userId,
|
|
1380
|
-
{ text: message.text },
|
|
1381
|
-
body.connectionId ? String(body.connectionId) : void 0
|
|
1382
|
-
);
|
|
1383
1411
|
return success({ success: true });
|
|
1384
1412
|
}
|
|
1385
1413
|
if (channel === "facebook" && subPath === "/send" && method === "POST") {
|
|
@@ -1447,6 +1475,44 @@ function buildBinding(alias, peripheralId) {
|
|
|
1447
1475
|
updatedAt: now2()
|
|
1448
1476
|
};
|
|
1449
1477
|
}
|
|
1478
|
+
var DEFAULT_CAPABILITIES = {
|
|
1479
|
+
paperWidth: "80mm",
|
|
1480
|
+
columns: 48,
|
|
1481
|
+
dotsPerLine: 576,
|
|
1482
|
+
renderMode: "text",
|
|
1483
|
+
textEncoding: "utf8",
|
|
1484
|
+
unicodeText: false,
|
|
1485
|
+
thaiText: false,
|
|
1486
|
+
bold: true,
|
|
1487
|
+
textSizes: ["small", "normal", "large"],
|
|
1488
|
+
barcodeFormats: ["qr"],
|
|
1489
|
+
rasterImages: true,
|
|
1490
|
+
cut: true,
|
|
1491
|
+
cashDrawer: false
|
|
1492
|
+
};
|
|
1493
|
+
function handleCapabilities(request) {
|
|
1494
|
+
const url = new URL(request.url);
|
|
1495
|
+
const alias = url.searchParams.get("alias");
|
|
1496
|
+
const deviceId = url.searchParams.get("deviceId");
|
|
1497
|
+
let peripheralId = url.searchParams.get("peripheralId");
|
|
1498
|
+
if (alias) {
|
|
1499
|
+
const binding = state.edgeBindings.get(alias);
|
|
1500
|
+
if (!binding) return bindingNotFound(alias);
|
|
1501
|
+
peripheralId = binding.peripheral?.id ?? null;
|
|
1502
|
+
}
|
|
1503
|
+
if (peripheralId) {
|
|
1504
|
+
const peripheral = findPeripheral(peripheralId);
|
|
1505
|
+
if (!peripheral) {
|
|
1506
|
+
return edgeFailure("Peripheral not found or its device is not granted to this project", 404);
|
|
1507
|
+
}
|
|
1508
|
+
const seeded = state.edgePeripheralCapabilities.get(peripheralId);
|
|
1509
|
+
return success({ capabilities: seeded ?? state.edgeCapabilities ?? DEFAULT_CAPABILITIES });
|
|
1510
|
+
}
|
|
1511
|
+
if (deviceId && !state.edgeDevices.some((d) => d.id === deviceId)) {
|
|
1512
|
+
return edgeFailure("Project does not have access to this device", 403);
|
|
1513
|
+
}
|
|
1514
|
+
return success({ capabilities: state.edgeCapabilities ?? DEFAULT_CAPABILITIES });
|
|
1515
|
+
}
|
|
1450
1516
|
function handleListDevices() {
|
|
1451
1517
|
return success({ devices: [...state.edgeDevices] });
|
|
1452
1518
|
}
|
|
@@ -1550,6 +1616,9 @@ async function handleEdgeRequest(request, subPath) {
|
|
|
1550
1616
|
if (subPath === "/print" && method === "POST") {
|
|
1551
1617
|
return handlePrint(request);
|
|
1552
1618
|
}
|
|
1619
|
+
if (subPath === "/capabilities" && method === "GET") {
|
|
1620
|
+
return handleCapabilities(request);
|
|
1621
|
+
}
|
|
1553
1622
|
if (subPath === "/devices" && method === "GET") {
|
|
1554
1623
|
return handleListDevices();
|
|
1555
1624
|
}
|
package/dist/setup.mjs
CHANGED
|
@@ -33,6 +33,8 @@ var state = globalSingleton("state", () => ({
|
|
|
33
33
|
edgeDevices: [],
|
|
34
34
|
edgePeripherals: [],
|
|
35
35
|
edgeBindings: /* @__PURE__ */ new Map(),
|
|
36
|
+
edgeCapabilities: null,
|
|
37
|
+
edgePeripheralCapabilities: /* @__PURE__ */ new Map(),
|
|
36
38
|
allowNetwork: false
|
|
37
39
|
}));
|
|
38
40
|
function requireDb() {
|
|
@@ -1297,6 +1299,15 @@ async function handleStorageRequest(request, url) {
|
|
|
1297
1299
|
|
|
1298
1300
|
// src/simulator/messaging.ts
|
|
1299
1301
|
import crypto7 from "crypto";
|
|
1302
|
+
function isValidLineImageUrl(url) {
|
|
1303
|
+
if (url.length > 2e3 || !url.startsWith("https://")) return false;
|
|
1304
|
+
try {
|
|
1305
|
+
new URL(url);
|
|
1306
|
+
return true;
|
|
1307
|
+
} catch {
|
|
1308
|
+
return false;
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1300
1311
|
function captureMessage(channel, recipient, body, connectionId) {
|
|
1301
1312
|
const message = {
|
|
1302
1313
|
channel,
|
|
@@ -1306,7 +1317,9 @@ function captureMessage(channel, recipient, body, connectionId) {
|
|
|
1306
1317
|
blocks: body.blocks,
|
|
1307
1318
|
threadTs: body.threadTs ? String(body.threadTs) : void 0,
|
|
1308
1319
|
messagingType: body.messagingType ? String(body.messagingType) : void 0,
|
|
1309
|
-
tag: body.tag ? String(body.tag) : void 0
|
|
1320
|
+
tag: body.tag ? String(body.tag) : void 0,
|
|
1321
|
+
originalContentUrl: body.originalContentUrl ? String(body.originalContentUrl) : void 0,
|
|
1322
|
+
previewImageUrl: body.previewImageUrl ? String(body.previewImageUrl) : void 0
|
|
1310
1323
|
},
|
|
1311
1324
|
connectionId,
|
|
1312
1325
|
sentAt: /* @__PURE__ */ new Date()
|
|
@@ -1344,18 +1357,33 @@ async function handleMessagingRequest(request, channel, subPath) {
|
|
|
1344
1357
|
const userId = String(body.userId ?? "");
|
|
1345
1358
|
const message = body.message;
|
|
1346
1359
|
if (!userId) return failure("userId is required");
|
|
1347
|
-
if (!message || message.type !== "text"
|
|
1348
|
-
return failure("message must be { type: 'text',
|
|
1360
|
+
if (!message || message.type !== "text" && message.type !== "image") {
|
|
1361
|
+
return failure("message must be { type: 'text' | 'image', ... }");
|
|
1349
1362
|
}
|
|
1350
|
-
|
|
1351
|
-
|
|
1363
|
+
const connectionId = body.connectionId ? String(body.connectionId) : void 0;
|
|
1364
|
+
if (message.type === "text") {
|
|
1365
|
+
if (!message.text) {
|
|
1366
|
+
return failure("message must be { type: 'text', text: string }");
|
|
1367
|
+
}
|
|
1368
|
+
if (message.text.length > 5e3) {
|
|
1369
|
+
return failure("message text exceeds maximum length");
|
|
1370
|
+
}
|
|
1371
|
+
captureMessage("line", userId, { text: message.text }, connectionId);
|
|
1372
|
+
} else {
|
|
1373
|
+
const originalContentUrl = message.originalContentUrl;
|
|
1374
|
+
if (!originalContentUrl || !isValidLineImageUrl(originalContentUrl)) {
|
|
1375
|
+
return failure("originalContentUrl must be a valid HTTPS URL (max 2000 chars)");
|
|
1376
|
+
}
|
|
1377
|
+
if (message.previewImageUrl !== void 0 && !isValidLineImageUrl(message.previewImageUrl)) {
|
|
1378
|
+
return failure("previewImageUrl must be a valid HTTPS URL (max 2000 chars)");
|
|
1379
|
+
}
|
|
1380
|
+
captureMessage(
|
|
1381
|
+
"line",
|
|
1382
|
+
userId,
|
|
1383
|
+
{ originalContentUrl, previewImageUrl: message.previewImageUrl ?? originalContentUrl },
|
|
1384
|
+
connectionId
|
|
1385
|
+
);
|
|
1352
1386
|
}
|
|
1353
|
-
captureMessage(
|
|
1354
|
-
"line",
|
|
1355
|
-
userId,
|
|
1356
|
-
{ text: message.text },
|
|
1357
|
-
body.connectionId ? String(body.connectionId) : void 0
|
|
1358
|
-
);
|
|
1359
1387
|
return success({ success: true });
|
|
1360
1388
|
}
|
|
1361
1389
|
if (channel === "facebook" && subPath === "/send" && method === "POST") {
|
|
@@ -1423,6 +1451,44 @@ function buildBinding(alias, peripheralId) {
|
|
|
1423
1451
|
updatedAt: now2()
|
|
1424
1452
|
};
|
|
1425
1453
|
}
|
|
1454
|
+
var DEFAULT_CAPABILITIES = {
|
|
1455
|
+
paperWidth: "80mm",
|
|
1456
|
+
columns: 48,
|
|
1457
|
+
dotsPerLine: 576,
|
|
1458
|
+
renderMode: "text",
|
|
1459
|
+
textEncoding: "utf8",
|
|
1460
|
+
unicodeText: false,
|
|
1461
|
+
thaiText: false,
|
|
1462
|
+
bold: true,
|
|
1463
|
+
textSizes: ["small", "normal", "large"],
|
|
1464
|
+
barcodeFormats: ["qr"],
|
|
1465
|
+
rasterImages: true,
|
|
1466
|
+
cut: true,
|
|
1467
|
+
cashDrawer: false
|
|
1468
|
+
};
|
|
1469
|
+
function handleCapabilities(request) {
|
|
1470
|
+
const url = new URL(request.url);
|
|
1471
|
+
const alias = url.searchParams.get("alias");
|
|
1472
|
+
const deviceId = url.searchParams.get("deviceId");
|
|
1473
|
+
let peripheralId = url.searchParams.get("peripheralId");
|
|
1474
|
+
if (alias) {
|
|
1475
|
+
const binding = state.edgeBindings.get(alias);
|
|
1476
|
+
if (!binding) return bindingNotFound(alias);
|
|
1477
|
+
peripheralId = binding.peripheral?.id ?? null;
|
|
1478
|
+
}
|
|
1479
|
+
if (peripheralId) {
|
|
1480
|
+
const peripheral = findPeripheral(peripheralId);
|
|
1481
|
+
if (!peripheral) {
|
|
1482
|
+
return edgeFailure("Peripheral not found or its device is not granted to this project", 404);
|
|
1483
|
+
}
|
|
1484
|
+
const seeded = state.edgePeripheralCapabilities.get(peripheralId);
|
|
1485
|
+
return success({ capabilities: seeded ?? state.edgeCapabilities ?? DEFAULT_CAPABILITIES });
|
|
1486
|
+
}
|
|
1487
|
+
if (deviceId && !state.edgeDevices.some((d) => d.id === deviceId)) {
|
|
1488
|
+
return edgeFailure("Project does not have access to this device", 403);
|
|
1489
|
+
}
|
|
1490
|
+
return success({ capabilities: state.edgeCapabilities ?? DEFAULT_CAPABILITIES });
|
|
1491
|
+
}
|
|
1426
1492
|
function handleListDevices() {
|
|
1427
1493
|
return success({ devices: [...state.edgeDevices] });
|
|
1428
1494
|
}
|
|
@@ -1526,6 +1592,9 @@ async function handleEdgeRequest(request, subPath) {
|
|
|
1526
1592
|
if (subPath === "/print" && method === "POST") {
|
|
1527
1593
|
return handlePrint(request);
|
|
1528
1594
|
}
|
|
1595
|
+
if (subPath === "/capabilities" && method === "GET") {
|
|
1596
|
+
return handleCapabilities(request);
|
|
1597
|
+
}
|
|
1529
1598
|
if (subPath === "/devices" && method === "GET") {
|
|
1530
1599
|
return handleListDevices();
|
|
1531
1600
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stardeck-customer-apps/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Vitest test harness for Stardeck customer apps — in-process Postgres (PGlite) plus a control-plane simulator so the real Stardeck SDKs run unmodified in tests",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
"@electric-sql/pglite": "^0.3.0"
|
|
66
66
|
},
|
|
67
67
|
"peerDependencies": {
|
|
68
|
-
"@stardeck-customer-apps/integrations-sdk": ">=1.6.0",
|
|
69
68
|
"@stardeck-customer-apps/edge-sdk": ">=1.4.0",
|
|
69
|
+
"@stardeck-customer-apps/integrations-sdk": ">=1.6.0",
|
|
70
70
|
"@stardeck-customer-apps/payments-sdk": ">=0.1.0",
|
|
71
71
|
"@stardeck-customer-apps/storage-sdk": ">=0.1.0",
|
|
72
72
|
"next": "^14.0.0 || ^15.0.0 || ^16.0.0",
|
|
@@ -93,8 +93,8 @@
|
|
|
93
93
|
"@eslint/js": "^9.0.0",
|
|
94
94
|
"@neondatabase/serverless": "^1.0.0",
|
|
95
95
|
"@stardeck-customer-apps/data-store-sdk": "*",
|
|
96
|
-
"@stardeck-customer-apps/email-sdk": "*",
|
|
97
96
|
"@stardeck-customer-apps/edge-sdk": "*",
|
|
97
|
+
"@stardeck-customer-apps/email-sdk": "*",
|
|
98
98
|
"@stardeck-customer-apps/integrations-sdk": "*",
|
|
99
99
|
"@stardeck-customer-apps/payments-sdk": "*",
|
|
100
100
|
"@stardeck-customer-apps/storage-sdk": "*",
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
"@types/node": "^24.10.1",
|
|
103
103
|
"kysely": "^0.27.0",
|
|
104
104
|
"kysely-neon": "^2.0.0",
|
|
105
|
-
"next": "^
|
|
105
|
+
"next": "^16.2.12",
|
|
106
106
|
"tsup": "^8.0.0",
|
|
107
107
|
"typescript": "^5.0.0",
|
|
108
108
|
"typescript-eslint": "^8.0.0",
|