@await-widget/runtime 0.0.19 → 0.0.21

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.
@@ -0,0 +1,302 @@
1
+ #!/usr/bin/env node
2
+ import crypto from "node:crypto";
3
+ import fs from "node:fs";
4
+ import http from "node:http";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+
8
+ const defaultPort = 4343;
9
+ const zipDosTime = 0;
10
+ const zipDosDate = 33;
11
+ const excludedNames = new Set([".git", ".build", "node_modules"]);
12
+ const crcTable = makeCrcTable();
13
+
14
+ function main() {
15
+ const [command, ...args] = process.argv.slice(2);
16
+ if (command === "dev") {
17
+ startDevServer(args);
18
+ return;
19
+ }
20
+ printHelp();
21
+ }
22
+
23
+ function startDevServer(args) {
24
+ const options = parseDevArgs(args);
25
+ const root = path.resolve(options.root);
26
+ const stats = fs.statSync(root);
27
+ if (!stats.isDirectory()) {
28
+ throw new Error(`${root} is not a directory`);
29
+ }
30
+
31
+ const token = crypto.randomBytes(6).toString("hex");
32
+ const server = http.createServer((request, response) => {
33
+ handleRequest({request, response, root, token});
34
+ });
35
+ server.listen(options.port, "0.0.0.0", () => {
36
+ const address = server.address();
37
+ const port = typeof address === "object" && address ? address.port : options.port;
38
+ console.log(`Await dev server serving ${root}`);
39
+ printServerUrls(port, token);
40
+ console.log("Press Ctrl+C to stop.");
41
+ });
42
+ }
43
+
44
+ function parseDevArgs(args) {
45
+ let root = ".";
46
+ let port = defaultPort;
47
+ for (let index = 0; index < args.length; index += 1) {
48
+ const arg = args[index];
49
+ if (arg === "--port") {
50
+ port = Number(args[index + 1]);
51
+ index += 1;
52
+ continue;
53
+ }
54
+ if (arg === "--help" || arg === "-h") {
55
+ printHelp();
56
+ process.exit(0);
57
+ }
58
+ root = arg;
59
+ }
60
+ return {root, port};
61
+ }
62
+
63
+ function handleRequest({request, response, root, token}) {
64
+ const url = new URL(request.url ?? "/", "http://localhost");
65
+ if (url.pathname === "/") {
66
+ sendText(response, `Await dev server\n${urlForRequest(request, token)}\n`);
67
+ return;
68
+ }
69
+ if (url.searchParams.get("token") !== token) {
70
+ sendText(response, "Forbidden\n", 403);
71
+ return;
72
+ }
73
+ if (url.pathname === "/await-dev/version") {
74
+ sendJson(response, {
75
+ name: path.basename(root),
76
+ version: projectVersion(root),
77
+ });
78
+ return;
79
+ }
80
+ if (url.pathname === "/await-dev/snapshot.zip") {
81
+ const archive = zipFiles(scanFiles(root));
82
+ response.writeHead(200, {
83
+ "Content-Type": "application/zip",
84
+ "Content-Length": archive.length,
85
+ "Cache-Control": "no-store",
86
+ });
87
+ response.end(archive);
88
+ return;
89
+ }
90
+ sendText(response, "Not Found\n", 404);
91
+ }
92
+
93
+ function projectVersion(root) {
94
+ const hash = crypto.createHash("sha256");
95
+ for (const file of scanFiles(root)) {
96
+ hash.update(file.relativePath);
97
+ hash.update("\0");
98
+ hash.update(String(file.size));
99
+ hash.update("\0");
100
+ hash.update(String(Math.trunc(file.mtimeMs)));
101
+ hash.update("\0");
102
+ }
103
+ return `sha256-${hash.digest("hex")}`;
104
+ }
105
+
106
+ function scanFiles(root) {
107
+ const files = [];
108
+ walk(root, "", files);
109
+ return files;
110
+ }
111
+
112
+ function walk(directory, relativeDirectory, files) {
113
+ const entries = fs.readdirSync(directory, {withFileTypes: true})
114
+ .sort((left, right) => left.name.localeCompare(right.name));
115
+ for (const entry of entries) {
116
+ if (entry.name.startsWith(".") || excludedNames.has(entry.name)) {
117
+ continue;
118
+ }
119
+ const relativePath = relativeDirectory ? `${relativeDirectory}/${entry.name}` : entry.name;
120
+ const absolutePath = path.join(directory, entry.name);
121
+ if (entry.isDirectory()) {
122
+ walk(absolutePath, relativePath, files);
123
+ continue;
124
+ }
125
+ if (!entry.isFile()) {
126
+ continue;
127
+ }
128
+ const stats = fs.statSync(absolutePath);
129
+ files.push({
130
+ absolutePath,
131
+ relativePath,
132
+ size: stats.size,
133
+ mtimeMs: stats.mtimeMs,
134
+ });
135
+ }
136
+ }
137
+
138
+ function zipFiles(files) {
139
+ const localParts = [];
140
+ const centralParts = [];
141
+ let offset = 0;
142
+ for (const file of files) {
143
+ const name = Buffer.from(file.relativePath, "utf8");
144
+ const data = fs.readFileSync(file.absolutePath);
145
+ const crc = crc32(data);
146
+ const localHeader = Buffer.concat([
147
+ u32(0x04034B50),
148
+ u16(20),
149
+ u16(0x0800),
150
+ u16(0),
151
+ u16(zipDosTime),
152
+ u16(zipDosDate),
153
+ u32(crc),
154
+ u32(data.length),
155
+ u32(data.length),
156
+ u16(name.length),
157
+ u16(0),
158
+ name,
159
+ ]);
160
+ localParts.push(localHeader, data);
161
+ centralParts.push(Buffer.concat([
162
+ u32(0x02014B50),
163
+ u16(20),
164
+ u16(20),
165
+ u16(0x0800),
166
+ u16(0),
167
+ u16(zipDosTime),
168
+ u16(zipDosDate),
169
+ u32(crc),
170
+ u32(data.length),
171
+ u32(data.length),
172
+ u16(name.length),
173
+ u16(0),
174
+ u16(0),
175
+ u16(0),
176
+ u16(0),
177
+ u32(0),
178
+ u32(offset),
179
+ name,
180
+ ]));
181
+ offset += localHeader.length + data.length;
182
+ }
183
+ const centralDirectory = Buffer.concat(centralParts);
184
+ const end = Buffer.concat([
185
+ u32(0x06054B50),
186
+ u16(0),
187
+ u16(0),
188
+ u16(files.length),
189
+ u16(files.length),
190
+ u32(centralDirectory.length),
191
+ u32(offset),
192
+ u16(0),
193
+ ]);
194
+ return Buffer.concat([...localParts, centralDirectory, end]);
195
+ }
196
+
197
+ function makeCrcTable() {
198
+ const table = new Uint32Array(256);
199
+ for (let index = 0; index < table.length; index += 1) {
200
+ let value = index;
201
+ for (let bit = 0; bit < 8; bit += 1) {
202
+ value = value & 1 ? 0xEDB88320 ^ (value >>> 1) : value >>> 1;
203
+ }
204
+ table[index] = value >>> 0;
205
+ }
206
+ return table;
207
+ }
208
+
209
+ function crc32(data) {
210
+ let crc = 0xFFFFFFFF;
211
+ for (const byte of data) {
212
+ crc = crcTable[(crc ^ byte) & 0xFF] ^ (crc >>> 8);
213
+ }
214
+ return (crc ^ 0xFFFFFFFF) >>> 0;
215
+ }
216
+
217
+ function u16(value) {
218
+ const buffer = Buffer.allocUnsafe(2);
219
+ buffer.writeUInt16LE(value);
220
+ return buffer;
221
+ }
222
+
223
+ function u32(value) {
224
+ const buffer = Buffer.allocUnsafe(4);
225
+ buffer.writeUInt32LE(value);
226
+ return buffer;
227
+ }
228
+
229
+ function sendJson(response, value) {
230
+ const body = Buffer.from(`${JSON.stringify(value)}\n`);
231
+ response.writeHead(200, {
232
+ "Content-Type": "application/json",
233
+ "Content-Length": body.length,
234
+ "Cache-Control": "no-store",
235
+ });
236
+ response.end(body);
237
+ }
238
+
239
+ function sendText(response, value, status = 200) {
240
+ response.writeHead(status, {
241
+ "Content-Type": "text/plain; charset=utf-8",
242
+ "Cache-Control": "no-store",
243
+ });
244
+ response.end(value);
245
+ }
246
+
247
+ function printServerUrls(port, token) {
248
+ const networkUrls = localNetworkUrls(port, token);
249
+ const recommended = networkUrls.find(({address}) => isPrivateIPv4(address)) ?? networkUrls[0];
250
+ if (recommended) {
251
+ console.log("Paste this URL into Await on iPhone:");
252
+ console.log(recommended.url);
253
+ }
254
+ const otherUrls = [
255
+ {url: `http://127.0.0.1:${port}?token=${token}`},
256
+ ...networkUrls.filter(item => item !== recommended),
257
+ ];
258
+ if (otherUrls.length > 0) {
259
+ console.log("Other network interfaces, usually not needed:");
260
+ for (const item of otherUrls) {
261
+ console.log(item.url);
262
+ }
263
+ }
264
+ }
265
+
266
+ function localNetworkUrls(port, token) {
267
+ const urls = [];
268
+ for (const interfaces of Object.values(os.networkInterfaces())) {
269
+ for (const item of interfaces ?? []) {
270
+ if (item.family === "IPv4" && !item.internal) {
271
+ urls.push({
272
+ address: item.address,
273
+ url: `http://${item.address}:${port}?token=${token}`,
274
+ });
275
+ }
276
+ }
277
+ }
278
+ return urls;
279
+ }
280
+
281
+ function isPrivateIPv4(address) {
282
+ const parts = address.split(".").map(part => Number(part));
283
+ if (parts.length !== 4 || parts.some(part => !Number.isInteger(part))) {
284
+ return false;
285
+ }
286
+ return parts[0] === 10 ||
287
+ (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) ||
288
+ (parts[0] === 192 && parts[1] === 168);
289
+ }
290
+
291
+ function urlForRequest(request, token) {
292
+ const host = request.headers.host ?? `127.0.0.1:${defaultPort}`;
293
+ return `http://${host}?token=${token}`;
294
+ }
295
+
296
+ function printHelp() {
297
+ console.log(`Usage:
298
+ await-widget dev [directory] [--port ${defaultPort}]
299
+ `);
300
+ }
301
+
302
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@await-widget/runtime",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "description": "TypeScript declarations for Await widgets.",
5
5
  "license": "MIT",
6
6
  "author": "LitoMore",
@@ -13,6 +13,7 @@
13
13
  "access": "public"
14
14
  },
15
15
  "files": [
16
+ "bin/*.js",
16
17
  "types/*.d.ts"
17
18
  ],
18
19
  "types": "./types/index.d.ts",
@@ -24,10 +25,14 @@
24
25
  "scripts": {
25
26
  "format:types": "xo --fix types/*.d.ts",
26
27
  "test": "tsc --noEmit -p test/tsconfig.json",
27
- "pack:dry": "npm --cache .npm-cache pack --dry-run"
28
+ "pack:dry": "npm --cache .npm-cache pack --dry-run",
29
+ "cli:test": "node bin/await-widget.js --help"
28
30
  },
29
31
  "devDependencies": {
30
32
  "typescript": "^6.0.2",
31
33
  "xo": "^2.0.2"
34
+ },
35
+ "bin": {
36
+ "await-widget": "bin/await-widget.js"
32
37
  }
33
38
  }
package/types/await.d.ts CHANGED
@@ -227,6 +227,15 @@ declare module 'await' {
227
227
  children: NativeView[];
228
228
  },
229
229
  ): NativeView;
230
+ /** This is a fallback for Ticker on iOS 18 and earlier. */
231
+ export function ClockSecond(
232
+ props: ClockSecondValue &
233
+ ID &
234
+ Mods & {
235
+ children: NativeView[];
236
+ },
237
+ ): NativeView;
238
+ /** Available on iOS 26 and later. */
230
239
  export function Ticker(
231
240
  props: TickerValue &
232
241
  ID &
package/types/bridge.d.ts CHANGED
@@ -156,6 +156,7 @@ export declare const AwaitEnv: {
156
156
  readonly id: string;
157
157
  readonly tag: number;
158
158
  readonly host: 'app' | 'widget';
159
+ readonly version: string;
159
160
  test(...args: unknown[]): unknown;
160
161
  };
161
162
  type AwaitGlobal = typeof Await;
package/types/prop.d.ts CHANGED
@@ -156,6 +156,10 @@ type TickerValue = {
156
156
  style: TickerStyle;
157
157
  };
158
158
 
159
+ type ClockSecondValue = {
160
+ size: Size;
161
+ };
162
+
159
163
  type FlipValue = {
160
164
  index: number;
161
165
  delta: number;