toyon 0.10.0 → 0.12.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.
Files changed (31) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +489 -2131
  3. package/dist/daemon.js +3441 -1012
  4. package/dist/helper-stub +0 -0
  5. package/dist/shell/assets/{Editor-DL0q1Bu5.js → Editor-ofBOHWJt.js} +3 -3
  6. package/dist/shell/assets/XTerm-C_Gv5uo2.js +58 -0
  7. package/dist/shell/assets/{cssMode-Et22hXpa.js → cssMode-LqxC-sXw.js} +1 -1
  8. package/dist/shell/assets/{freemarker2-Clz20PcM.js → freemarker2-DUqPZFAL.js} +1 -1
  9. package/dist/shell/assets/{grammar-CPvdB3az.js → grammar-CFa79rtH.js} +1 -1
  10. package/dist/shell/assets/{handlebars-B4nvXAx8.js → handlebars-CF9us5mA.js} +1 -1
  11. package/dist/shell/assets/{html-DPIGgBLK.js → html-SO575qLW.js} +1 -1
  12. package/dist/shell/assets/{htmlMode-C2LaVuEX.js → htmlMode-XUsZRqmU.js} +1 -1
  13. package/dist/shell/assets/index-BYJYiHrs.css +1 -0
  14. package/dist/shell/assets/index-Brx-QOIQ.js +266 -0
  15. package/dist/shell/assets/{javascript-CnwJfTbN.js → javascript-4ER2NhcO.js} +1 -1
  16. package/dist/shell/assets/{jsonMode-8znCTiqR.js → jsonMode-D-UOjsYN.js} +1 -1
  17. package/dist/shell/assets/{liquid-Bdm-1CUh.js → liquid-CMXc0SdF.js} +1 -1
  18. package/dist/shell/assets/{lspLanguageFeatures-B1DMU7yH.js → lspLanguageFeatures-D4Z0LyWk.js} +1 -1
  19. package/dist/shell/assets/{mdx-Ktcqo9Jf.js → mdx-Dc4E4gPJ.js} +1 -1
  20. package/dist/shell/assets/{python-QX--d8LR.js → python-DhCnlsga.js} +1 -1
  21. package/dist/shell/assets/{razor-C0LrXteG.js → razor-CGRu_Zyc.js} +1 -1
  22. package/dist/shell/assets/{tsMode-BtYCp6db.js → tsMode-BoDg-EAl.js} +1 -1
  23. package/dist/shell/assets/{xml-BXbaQ9MG.js → xml-Dl50xhA0.js} +1 -1
  24. package/dist/shell/assets/{yaml-C6FhaU_C.js → yaml-CoqiVhBi.js} +1 -1
  25. package/dist/shell/index.html +5 -29
  26. package/dist/shell/offline.html +219 -0
  27. package/dist/shell/sw.js +32 -3
  28. package/package.json +1 -1
  29. package/dist/shell/assets/XTerm-C9YH4Lh2.js +0 -58
  30. package/dist/shell/assets/index-2QusTgw-.js +0 -262
  31. package/dist/shell/assets/index-TaJLoLSd.css +0 -1
package/dist/cli.js CHANGED
@@ -2,18 +2,124 @@
2
2
  // @bun
3
3
 
4
4
  // src/cli.ts
5
- import { existsSync as existsSync10, statSync as statSync4 } from "fs";
5
+ import { existsSync as existsSync11, statSync as statSync4 } from "fs";
6
6
  import { join as join7, resolve } from "path";
7
+ // package.json
8
+ var package_default = {
9
+ name: "toyon",
10
+ version: "0.12.0",
11
+ description: "Toyon is where coding agents work on your web app while you check each change by using it. Every chat gets its own running copy with a live preview.",
12
+ license: "MIT",
13
+ type: "module",
14
+ homepage: "https://toyon.dev",
15
+ repository: {
16
+ type: "git",
17
+ url: "git+https://github.com/toyon-dev/toyon.git"
18
+ },
19
+ bugs: "https://github.com/toyon-dev/toyon/issues",
20
+ keywords: [
21
+ "ai",
22
+ "agents",
23
+ "claude-code",
24
+ "codex",
25
+ "opencode",
26
+ "git",
27
+ "worktree",
28
+ "preview",
29
+ "dev-server"
30
+ ],
31
+ bin: {
32
+ toyon: "bin/toyon.js"
33
+ },
34
+ files: [
35
+ "bin",
36
+ "dist",
37
+ "cloud"
38
+ ],
39
+ engines: {
40
+ node: ">=18"
41
+ },
42
+ os: [
43
+ "darwin",
44
+ "linux"
45
+ ],
46
+ scripts: {
47
+ prepublishOnly: "bun ../../scripts/pack.ts"
48
+ },
49
+ dependencies: {
50
+ bun: "1.4.2",
51
+ "bun-pty": "0.4.10"
52
+ },
53
+ devDependencies: {
54
+ "@toyon/shared": "workspace:*"
55
+ }
56
+ };
57
+
58
+ // src/app.ts
59
+ import { spawn as spawn3, spawnSync } from "child_process";
60
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
61
+ import { homedir as homedir2 } from "os";
62
+ import { join as join3 } from "path";
63
+
64
+ // src/daemon.ts
65
+ import { spawn } from "child_process";
66
+ import { existsSync as existsSync2, mkdirSync, openSync, readFileSync } from "fs";
67
+ import { homedir } from "os";
68
+ import { join as join2 } from "path";
69
+
70
+ // ../shared/src/knock.ts
71
+ var KNOCK_TTL_MS = 5 * 60000;
72
+ var machineLink = (host) => `https://${host}/`;
73
+ var isTailnetName = (host) => host.endsWith(".ts.net");
74
+ var listed = (names) => names.length < 2 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
75
+ function tailnetLine(phones) {
76
+ if (phones === null)
77
+ return { ok: true, text: "Your phone needs Tailscale on, signed in to the same tailnet." };
78
+ if (phones.length === 0) {
79
+ return {
80
+ ok: false,
81
+ text: "No phone has joined your tailnet. Install Tailscale on it and sign in to the same account first."
82
+ };
83
+ }
84
+ const on = phones.filter((p) => p.online).map((p) => p.name);
85
+ if (on.length > 0)
86
+ return { ok: true, text: `${listed(on)} ${on.length > 1 ? "are" : "is"} on your tailnet.` };
87
+ const off = phones.map((p) => p.name);
88
+ return {
89
+ ok: false,
90
+ text: `${listed(off)} ${off.length > 1 ? "are" : "is"} not answering on your tailnet. Check Tailscale is on there.`
91
+ };
92
+ }
7
93
 
8
94
  // ../shared/src/daemon.ts
95
+ var APP_SCHEME = "toyon";
96
+ var START_LINK = `${APP_SCHEME}://start`;
97
+ var LSREGISTER = "/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister";
9
98
  var DAEMON_FILES = {
10
99
  token: "token",
100
+ helper: "Toyon.app",
101
+ launcherLog: "launcher.log",
11
102
  pid: "daemon.pid",
12
103
  state: "state.json",
13
104
  drafts: "drafts.json",
14
105
  log: "daemon.log",
15
106
  remote: "remote.json"
16
107
  };
108
+ function previewsLine(r) {
109
+ return addressedByPort(r.previews) ? `previews on ports ${PREVIEW_PORTS.from}-${PREVIEW_PORTS.to}` : `previews at ${r.previews}`;
110
+ }
111
+ function acceptRemote(view, policy) {
112
+ if (policy.remote === "off")
113
+ return "remote access is turned off by your organization's policy";
114
+ const host = view.host.toLowerCase();
115
+ if (!isRemoteHost(host))
116
+ return `${view.host} is not a name a front can answer for`;
117
+ if (policy.remote === "tailscale" && !isTailnetName(host)) {
118
+ return "your organization's policy allows remote access over Tailscale only";
119
+ }
120
+ const why = checkPreviews(view.previews, host);
121
+ return why ?? { host, previews: view.previews };
122
+ }
17
123
  var PREVIEW_PORTS = { from: 10001, to: 10008 };
18
124
  var DNS_NAME = /^(?=.{1,253}$)[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$/;
19
125
  function isRemoteHost(name) {
@@ -61,10 +167,6 @@ function parseRemote(text) {
61
167
  }
62
168
  var WS_CLOSE_UNAUTHORIZED = 4401;
63
169
 
64
- // ../shared/src/launcher.ts
65
- var LAUNCHER_ORIGIN = "https://toyon.cloud";
66
- var launcherAddLink = (machineOrigin) => `${LAUNCHER_ORIGIN}/#add=${encodeURIComponent(machineOrigin)}`;
67
-
68
170
  // ../../node_modules/.bun/zod@4.5.4/node_modules/zod/v4/core/util.js
69
171
  function getEnumValues(entries) {
70
172
  const numericValues = Object.values(entries).filter((v) => typeof v === "number");
@@ -5083,28 +5185,6 @@ function describeManaged(p) {
5083
5185
  return out;
5084
5186
  }
5085
5187
 
5086
- // ../shared/src/pair.ts
5087
- var isTailnetName = (host) => host.endsWith(".ts.net");
5088
- var listed = (names) => names.length < 2 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
5089
- function tailnetLine(phones) {
5090
- if (phones === null)
5091
- return { ok: true, text: "Your phone needs Tailscale on, signed in to the same tailnet." };
5092
- if (phones.length === 0) {
5093
- return {
5094
- ok: false,
5095
- text: "No phone has joined your tailnet. Install Tailscale on it and sign in to the same account first."
5096
- };
5097
- }
5098
- const on = phones.filter((p) => p.online).map((p) => p.name);
5099
- if (on.length > 0)
5100
- return { ok: true, text: `${listed(on)} ${on.length > 1 ? "are" : "is"} on your tailnet.` };
5101
- const off = phones.map((p) => p.name);
5102
- return {
5103
- ok: false,
5104
- text: `${listed(off)} ${off.length > 1 ? "are" : "is"} not answering on your tailnet. Check Tailscale is on there.`
5105
- };
5106
- }
5107
-
5108
5188
  // ../shared/src/ports.ts
5109
5189
  var DAEMON_DEFAULT_PORT = 4141;
5110
5190
 
@@ -5187,68 +5267,6 @@ function parseVersion(v) {
5187
5267
  return null;
5188
5268
  return { nums: [Number(m[1]), Number(m[2]), Number(m[3])], pre: m[4] ?? null };
5189
5269
  }
5190
- // package.json
5191
- var package_default = {
5192
- name: "toyon",
5193
- version: "0.10.0",
5194
- description: "Toyon is where coding agents work on your web app while you check each change by using it. Every chat gets its own running copy with a live preview.",
5195
- license: "MIT",
5196
- type: "module",
5197
- homepage: "https://toyon.dev",
5198
- repository: {
5199
- type: "git",
5200
- url: "git+https://github.com/toyon-dev/toyon.git"
5201
- },
5202
- bugs: "https://github.com/toyon-dev/toyon/issues",
5203
- keywords: [
5204
- "ai",
5205
- "agents",
5206
- "claude-code",
5207
- "codex",
5208
- "opencode",
5209
- "git",
5210
- "worktree",
5211
- "preview",
5212
- "dev-server"
5213
- ],
5214
- bin: {
5215
- toyon: "bin/toyon.js"
5216
- },
5217
- files: [
5218
- "bin",
5219
- "dist",
5220
- "cloud"
5221
- ],
5222
- engines: {
5223
- node: ">=18"
5224
- },
5225
- os: [
5226
- "darwin",
5227
- "linux"
5228
- ],
5229
- scripts: {
5230
- prepublishOnly: "bun ../../scripts/pack.ts"
5231
- },
5232
- dependencies: {
5233
- bun: "1.4.2",
5234
- "bun-pty": "0.4.10"
5235
- },
5236
- devDependencies: {
5237
- "@toyon/shared": "workspace:*"
5238
- }
5239
- };
5240
-
5241
- // src/app.ts
5242
- import { spawn as spawn2, spawnSync } from "child_process";
5243
- import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "fs";
5244
- import { homedir as homedir2 } from "os";
5245
- import { join as join3 } from "path";
5246
-
5247
- // src/daemon.ts
5248
- import { spawn } from "child_process";
5249
- import { existsSync as existsSync2, mkdirSync, openSync, readFileSync } from "fs";
5250
- import { homedir } from "os";
5251
- import { join as join2 } from "path";
5252
5270
 
5253
5271
  // src/layout.ts
5254
5272
  import { existsSync, realpathSync } from "fs";
@@ -5272,7 +5290,6 @@ function installOnPath() {
5272
5290
  }
5273
5291
  var restartCmd = restartCommand(method, package_default.version, method === "npx" ? installOnPath() : "none", process.env.npm_config_registry ?? null);
5274
5292
  var daemonEntry = packaged ? join(here, "daemon.js") : join(here, "../../daemon/src/index.ts");
5275
- var iconSvg = packaged ? join(here, "shell", "icon.svg") : join(here, "../../shell/public/icon.svg");
5276
5293
 
5277
5294
  // src/daemon.ts
5278
5295
  var port = Number(process.env.TOYON_PORT ?? DAEMON_DEFAULT_PORT);
@@ -5292,6 +5309,23 @@ async function health() {
5292
5309
  return null;
5293
5310
  }
5294
5311
  }
5312
+ async function daemonFetch(path, init = {}) {
5313
+ const token = readToken();
5314
+ if (!token)
5315
+ return null;
5316
+ try {
5317
+ return await fetch(`${base}${path}`, {
5318
+ method: init.method ?? (init.body === undefined ? "GET" : "POST"),
5319
+ headers: {
5320
+ authorization: `Bearer ${token}`,
5321
+ ...init.body === undefined ? {} : { "content-type": "application/json" }
5322
+ },
5323
+ body: init.body === undefined ? undefined : JSON.stringify(init.body)
5324
+ });
5325
+ } catch {
5326
+ return null;
5327
+ }
5328
+ }
5295
5329
  function readToken() {
5296
5330
  if (!existsSync2(tokenFile))
5297
5331
  return null;
@@ -5329,63 +5363,46 @@ async function startDaemon() {
5329
5363
  }
5330
5364
  return await health() !== null;
5331
5365
  }
5366
+ function behind(daemonVersion) {
5367
+ return daemonVersion === undefined || newer(package_default.version, daemonVersion);
5368
+ }
5369
+ async function ensureDaemon() {
5370
+ let running = await health();
5371
+ if (!running) {
5372
+ console.log("starting Toyon daemon\u2026");
5373
+ running = await startDaemon() ? await health() : null;
5374
+ if (!running) {
5375
+ console.error(`daemon failed to start; \`toyon logs\` shows why (${logFile})`);
5376
+ return null;
5377
+ }
5378
+ } else if (behind(running.version)) {
5379
+ console.log(`toyon: the running daemon is ${running.version ?? "older"}; \`${restartCmd}\` starts ${package_default.version}`);
5380
+ }
5381
+ const token = readToken();
5382
+ if (!token) {
5383
+ console.error("daemon token missing; `toyon doctor` says where it looked");
5384
+ return null;
5385
+ }
5386
+ return { token, url: shellUrl(token, running.branded === true), remote: running.remote ?? null };
5387
+ }
5388
+ function post(route, token, body) {
5389
+ return fetch(`${base}${route}`, {
5390
+ method: "POST",
5391
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
5392
+ body: JSON.stringify(body)
5393
+ });
5394
+ }
5332
5395
  function shellUrl(token, branded) {
5333
5396
  return branded ? `http://toyon.localhost/#token=${token}` : `http://toyon.localhost:${port}/#token=${token}`;
5334
5397
  }
5335
5398
 
5336
- // src/launcher.m
5337
- var launcher_default = `// The Toyon.app bundle's executable. A plain C stub that execs the launch script never hears
5338
- // about a file dropped on the Dock icon: that arrives as an Apple Event, which only an
5339
- // NSApplication receives. So this is the smallest Cocoa app there is: it collects the paths the
5340
- // event names, answers the event (Finder waits on the answer, and an exec before it would leave
5341
- // Finder hanging), and once launched execs the script with those paths. The open-files call comes
5342
- // before the finish-launching one, which is why the paths are collected first and the exec waits
5343
- // for the second. With nothing dropped the script runs with no arguments.
5344
- //
5345
- // Read into the CLI as text (app.ts) and compiled at \`toyon --install-app\` on the machine itself.
5346
- #import <Cocoa/Cocoa.h>
5347
- #include <libgen.h>
5348
- #include <mach-o/dyld.h>
5349
- #include <stdlib.h>
5350
- #include <unistd.h>
5351
-
5352
- @interface Launcher : NSObject <NSApplicationDelegate>
5353
- @property(strong) NSMutableArray<NSString *> *paths;
5354
- @end
5355
-
5356
- @implementation Launcher
5357
- - (void)application:(NSApplication *)app openFiles:(NSArray<NSString *> *)files {
5358
- [self.paths addObjectsFromArray:files];
5359
- [app replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
5360
- }
5361
- - (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app {
5362
- return YES;
5363
- }
5364
- - (void)applicationDidFinishLaunching:(NSNotification *)note {
5365
- char self_[4096];
5366
- uint32_t n = sizeof(self_);
5367
- if (_NSGetExecutablePath(self_, &n) != 0) exit(1);
5368
- NSString *script = [NSString stringWithFormat:@"%s/../Resources/launch.sh", dirname(self_)];
5369
- NSMutableArray<NSString *> *argv = [NSMutableArray arrayWithObjects:@"bash", script, nil];
5370
- [argv addObjectsFromArray:self.paths];
5371
- const char **cargv = calloc(argv.count + 1, sizeof(char *));
5372
- for (NSUInteger i = 0; i < argv.count; i++) cargv[i] = [argv[i] fileSystemRepresentation];
5373
- execv("/bin/bash", (char *const *)cargv);
5374
- exit(1);
5375
- }
5376
- @end
5377
-
5378
- int main(void) {
5379
- @autoreleasepool {
5380
- NSApplication *app = [NSApplication sharedApplication];
5381
- Launcher *launcher = [Launcher new];
5382
- launcher.paths = [NSMutableArray new];
5383
- app.delegate = launcher;
5384
- [app run];
5385
- }
5386
- return 0;
5399
+ // src/openUrl.ts
5400
+ import { spawn as spawn2 } from "child_process";
5401
+ function openUrl(url) {
5402
+ const child = spawn2(process.platform === "darwin" ? "open" : "xdg-open", [url], { stdio: "ignore" });
5403
+ child.on("error", () => {});
5404
+ child.unref();
5387
5405
  }
5388
- `;
5389
5406
 
5390
5407
  // src/app.ts
5391
5408
  var CHROMIUMS = ["Google Chrome", "Arc", "Brave Browser", "Microsoft Edge", "Chromium"];
@@ -5431,13 +5448,21 @@ function findPwaId(browser, manifestId) {
5431
5448
  }
5432
5449
  return null;
5433
5450
  }
5451
+ function showAppWindow(daemon) {
5452
+ const appUrl = shellUrl(daemon.token, false);
5453
+ console.log(`toyon: app window (${appUrl.split("#")[0]})`);
5454
+ if (openAppWindow(appUrl))
5455
+ return;
5456
+ console.log("no Chromium browser found; opening in default browser");
5457
+ openUrl(daemon.url);
5458
+ }
5434
5459
  function openAppWindow(appUrl) {
5435
5460
  const manifestId = new URL("/", appUrl).href;
5436
5461
  for (const app of CHROMIUMS) {
5437
5462
  if (spawnSync("open", ["-Ra", app]).status === 0) {
5438
5463
  const id = findPwaId(app, manifestId);
5439
5464
  const flag = id ? `--app-id=${id}` : `--app=${appUrl}`;
5440
- spawn2("open", ["-na", app, "--args", flag], { stdio: "ignore" }).unref();
5465
+ spawn3("open", ["-na", app, "--args", flag], { stdio: "ignore" }).unref();
5441
5466
  if (!id) {
5442
5467
  console.log(`tip: install Toyon as an app (browser menu, then Install, or the install button in the top bar when opened in a tab)`);
5443
5468
  console.log(` once installed, it gets a native-style title bar; \`toyon --app\` then launches the installed app`);
@@ -5447,145 +5472,11 @@ function openAppWindow(appUrl) {
5447
5472
  }
5448
5473
  return false;
5449
5474
  }
5450
- function installApp(appUrl) {
5451
- const manifestId = new URL("/", appUrl).href;
5452
- const appDir = join3(homedir2(), "Applications", "Toyon.app");
5453
- const macos = join3(appDir, "Contents", "MacOS");
5454
- const resources = join3(appDir, "Contents", "Resources");
5455
- rmSync(appDir, { recursive: true, force: true });
5456
- mkdirSync2(macos, { recursive: true });
5457
- mkdirSync2(resources, { recursive: true });
5458
- writeFileSync(join3(appDir, "Contents", "Info.plist"), `<?xml version="1.0" encoding="UTF-8"?>
5459
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
5460
- <plist version="1.0"><dict>
5461
- <key>CFBundleName</key><string>Toyon</string>
5462
- <key>CFBundleDisplayName</key><string>Toyon</string>
5463
- <key>CFBundleIdentifier</key><string>dev.toyon.app</string>
5464
- <key>CFBundleVersion</key><string>0.0.1</string>
5465
- <key>CFBundlePackageType</key><string>APPL</string>
5466
- <key>CFBundleExecutable</key><string>Toyon</string>
5467
- <key>CFBundleIconFile</key><string>AppIcon</string>
5468
- <!-- takes any file or folder dropped on it, without becoming the default opener for anything -->
5469
- <key>CFBundleDocumentTypes</key>
5470
- <array>
5471
- <dict>
5472
- <key>CFBundleTypeName</key><string>Anything</string>
5473
- <key>CFBundleTypeRole</key><string>Editor</string>
5474
- <key>LSHandlerRank</key><string>Alternate</string>
5475
- <key>LSItemContentTypes</key>
5476
- <array>
5477
- <string>public.item</string>
5478
- <string>public.folder</string>
5479
- </array>
5480
- </dict>
5481
- </array>
5482
- </dict></plist>
5483
- `);
5484
- const launcher = join3(resources, "launch.sh");
5485
- writeFileSync(launcher, `#!/bin/bash
5486
- # Finder launches get a minimal PATH; find bun ourselves
5487
- BUN=""
5488
- for CAND in "$HOME/.bun/bin/bun" /opt/homebrew/bin/bun /usr/local/bin/bun; do
5489
- [ -x "$CAND" ] && BUN="$CAND" && break
5490
- done
5491
-
5492
- # start the daemon if it isn't running
5493
- if ! curl -s --max-time 1 http://127.0.0.1:${port}/health >/dev/null 2>&1; then
5494
- if [ -n "$BUN" ] && [ -f "${daemonEntry}" ]; then
5495
- nohup "$BUN" run "${daemonEntry}" >> "$HOME/.toyon/daemon.log" 2>&1 &
5496
- for _ in $(seq 1 40); do
5497
- curl -s --max-time 1 http://127.0.0.1:${port}/health >/dev/null 2>&1 && break
5498
- sleep 0.25
5499
- done
5500
- fi
5501
- fi
5502
-
5503
- TOKEN=$(cat "$HOME/.toyon/token" 2>/dev/null)
5504
-
5505
- # what was dropped on the icon, or opened with it: each path goes to the daemon, which opens a
5506
- # project, a file in one, or a file on its own, and holds it for the window opened below. A
5507
- # refusal is logged in the daemon's words, since nothing here has a screen (the daemon says it in
5508
- # the shell too); a success is logged as its status alone, since the reply carries the file's text.
5509
- for P in "$@"; do
5510
- BODY=$("$BUN" -e 'console.log(JSON.stringify({ path: process.argv[1] }))' "$P" 2>/dev/null) || continue
5511
- REPLY=$(curl -s -o /dev/null -w '%{http_code}' -X POST -H "Authorization: Bearer $TOKEN" \\
5512
- -H "content-type: application/json" --data "$BODY" http://127.0.0.1:${port}/open 2>/dev/null)
5513
- if [ "$REPLY" != "200" ]; then
5514
- REPLY="$REPLY $(curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" \\
5515
- --data "$BODY" http://127.0.0.1:${port}/open 2>&1 | head -c 300)"
5516
- fi
5517
- echo "$(date '+%Y-%m-%d %H:%M:%S') open $P: $REPLY" >> "$HOME/.toyon/launcher.log"
5518
- done
5519
-
5520
- URL="http://toyon.localhost:${port}/#token=$TOKEN"
5521
- MANIFEST_ID="${manifestId}"
5522
- CACHE_DIR="$HOME/.toyon/pwa"
5523
- # installed PWA (native-style title bar) if any profile has it, else a plain app window.
5524
- # The app id lives in the browser's sync DB right before the manifest id; cache it once found.
5525
- find_pwa_id() { # $1 = data dir, $2 = cache file
5526
- local root="$HOME/Library/Application Support/$1" id p f
5527
- if [ -f "$2" ]; then
5528
- id=$(tr -d '[:space:]' < "$2")
5529
- for p in "$root"/*/; do [ -d "$p/Web Applications/Manifest Resources/$id" ] && { echo "$id"; return; }; done
5530
- fi
5531
- for p in "$root"/*/; do
5532
- for f in "$p/Sync Data/LevelDB/"*.log "$p/Sync Data/LevelDB/"*.ldb; do
5533
- [ -f "$f" ] || continue
5534
- # the record straddles newline bytes, so flatten before the line-based grep
5535
- id=$(LC_ALL=C tr '\\n\\0' ' ' < "$f" | LC_ALL=C grep -aoE "web_apps-dt-[a-p]{32}.{0,16}$MANIFEST_ID" 2>/dev/null | head -1 | LC_ALL=C sed -E 's/^web_apps-dt-([a-p]{32}).*/\\1/')
5536
- if [ -n "$id" ] && [ -d "$p/Web Applications/Manifest Resources/$id" ]; then
5537
- mkdir -p "$CACHE_DIR" && printf '%s' "$id" > "$2"; echo "$id"; return
5538
- fi
5539
- done
5540
- done
5541
- }
5542
- launch() { # $1 = browser name, $2 = data dir
5543
- local id; id=$(find_pwa_id "$2" "$CACHE_DIR/$(echo "$1" | tr -c 'A-Za-z0-9
5544
- ' '-').id")
5545
- if [ -n "$id" ]; then exec open -na "$1" --args --app-id="$id"; fi
5546
- exec open -na "$1" --args --app="$URL"
5547
- }
5548
- ${CHROMIUMS.map((a) => `if open -Ra "${a}" 2>/dev/null; then launch "${a}" "${DATA_DIRS[a]}"; fi`).join(`
5549
- `)}
5550
- exec open "$URL"
5551
- `);
5552
- chmodSync(launcher, 493);
5553
- const stub = join3(macos, "Toyon");
5554
- const src = join3(home, "launcher.m");
5555
- writeFileSync(src, launcher_default);
5556
- const cc = spawnSync("clang", ["-O2", "-fobjc-arc", "-framework", "Cocoa", "-o", stub, src], { stdio: "ignore" });
5557
- if (cc.status !== 0) {
5558
- writeFileSync(stub, `#!/bin/bash
5559
- exec /bin/bash "$(dirname "$0")/../Resources/launch.sh"
5560
- `);
5561
- chmodSync(stub, 493);
5562
- }
5563
- try {
5564
- const svg = iconSvg;
5565
- const tmp = join3(home, "iconset.tmp");
5566
- const iconset = join3(tmp, "AppIcon.iconset");
5567
- mkdirSync2(iconset, { recursive: true });
5568
- spawnSync("qlmanage", ["-t", "-s", "1024", "-o", tmp, svg], { stdio: "ignore" });
5569
- const big = join3(tmp, "icon.svg.png");
5570
- if (existsSync3(big)) {
5571
- for (const size of [16, 32, 64, 128, 256, 512, 1024]) {
5572
- spawnSync("sips", ["-z", String(size), String(size), big, "--out", join3(iconset, `icon_${size}x${size}.png`)], {
5573
- stdio: "ignore"
5574
- });
5575
- }
5576
- spawnSync("iconutil", ["-c", "icns", iconset, "-o", join3(resources, "AppIcon.icns")], { stdio: "ignore" });
5577
- }
5578
- } catch {}
5579
- spawnSync("xattr", ["-cr", appDir], { stdio: "ignore" });
5580
- const signed = spawnSync("codesign", ["--force", "--deep", "-s", "-", appDir], { stdio: "ignore" }).status === 0;
5581
- if (!signed)
5582
- console.warn("warning: could not codesign the app bundle; macOS may refuse to open it");
5583
- console.log(`installed ${appDir}; launch "Toyon" from Spotlight or drag it to the Dock`);
5584
- spawn2("open", ["-a", appDir], { stdio: "ignore" }).unref();
5585
- }
5586
5475
 
5587
5476
  // src/args.ts
5588
5477
  var VERBS = new Set([
5478
+ "start",
5479
+ "helper",
5589
5480
  "stop",
5590
5481
  "restart",
5591
5482
  "update",
@@ -5607,6 +5498,9 @@ function parseArgs(argv) {
5607
5498
  const [first, ...rest] = argv;
5608
5499
  if (first !== undefined && VERBS.has(first)) {
5609
5500
  switch (first) {
5501
+ case "helper":
5502
+ return { kind: "helper", args: rest };
5503
+ case "start":
5610
5504
  case "stop":
5611
5505
  case "restart":
5612
5506
  case "update":
@@ -5658,12 +5552,9 @@ function parseArgs(argv) {
5658
5552
  }
5659
5553
  let path = null;
5660
5554
  let app = false;
5661
- let installApp = false;
5662
5555
  for (const a of argv) {
5663
5556
  if (a === "--app" || a === "--pwa")
5664
5557
  app = true;
5665
- else if (a === "--install-app")
5666
- installApp = true;
5667
5558
  else if (a.startsWith("-"))
5668
5559
  return { kind: "error", message: `unknown option ${a}` };
5669
5560
  else if (path !== null)
@@ -5671,7 +5562,7 @@ function parseArgs(argv) {
5671
5562
  else
5672
5563
  path = a;
5673
5564
  }
5674
- return { kind: "open", path, app, installApp };
5565
+ return { kind: "open", path, app };
5675
5566
  }
5676
5567
  function parseLogs(rest) {
5677
5568
  let follow = false;
@@ -5739,6 +5630,7 @@ usage
5739
5630
  toyon [path] start the daemon if it is not running, register the repo at path
5740
5631
  (or open the file at path: in its project, or on its own)
5741
5632
  (default: the current directory) and open the shell
5633
+ toyon start start the daemon if it is not running, and open nothing
5742
5634
  toyon stop stop the daemon and every dev server and agent it runs
5743
5635
  toyon restart stop the daemon and start it again from what is installed now; every
5744
5636
  shell reconnects on its own
@@ -5757,9 +5649,9 @@ usage
5757
5649
  toyon remote --tailscale
5758
5650
  open the shell from your tailnet at this machine's Tailscale name:
5759
5651
  sets up tailscale serve for Toyon and ports 10001-10008
5760
- toyon pair print a QR code to scan with your phone's camera: it opens the shell
5761
- at the remote name, signed in, and adds it to toyon.cloud; the code
5762
- works once and lasts 2 minutes
5652
+ toyon pair show this machine's address as a QR code for your phone, and let in
5653
+ each device that opens it, from this terminal; Toyon's window asks
5654
+ the same question when one is open
5763
5655
  toyon deploy fly up <name> [--region code] [--repo url]
5764
5656
  run Toyon on your own Fly account at https://name.fly.dev with your own
5765
5657
  keys; --repo clones that repository onto it the first time
@@ -5770,7 +5662,6 @@ usage
5770
5662
 
5771
5663
  options for toyon [path]
5772
5664
  --app open a Chromium app window (the installed Toyon app when there is one)
5773
- --install-app write ~/Applications/Toyon.app and open it
5774
5665
 
5775
5666
  environment
5776
5667
  TOYON_HOME where state lives (default ~/.toyon)
@@ -5786,7 +5677,7 @@ policy
5786
5677
  // src/deploy/fly.ts
5787
5678
  import { randomBytes } from "crypto";
5788
5679
  import { Resolver } from "dns/promises";
5789
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, mkdtempSync, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
5680
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, mkdtempSync, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync3 } from "fs";
5790
5681
  import { tmpdir } from "os";
5791
5682
  import { join as join5 } from "path";
5792
5683
 
@@ -5839,14 +5730,6 @@ function safeUser() {
5839
5730
  }
5840
5731
  }
5841
5732
 
5842
- // src/openUrl.ts
5843
- import { spawn as spawn3 } from "child_process";
5844
- function openUrl(url) {
5845
- const child = spawn3(process.platform === "darwin" ? "open" : "xdg-open", [url], { stdio: "ignore" });
5846
- child.on("error", () => {});
5847
- child.unref();
5848
- }
5849
-
5850
5733
  // src/policy.ts
5851
5734
  function refusedByPolicy(verb, m) {
5852
5735
  console.error(`toyon: ${verb} is turned off by your organization's policy (${m.source ?? "policy"})`);
@@ -6075,7 +5958,7 @@ async function up(cmd, bin) {
6075
5958
  throw new DeployError("the deploy failed; flyctl's output above says why");
6076
5959
  }
6077
5960
  } finally {
6078
- rmSync2(ctx, { recursive: true, force: true });
5961
+ rmSync(ctx, { recursive: true, force: true });
6079
5962
  }
6080
5963
  console.log("waiting for the machine to answer");
6081
5964
  let up = false;
@@ -6088,15 +5971,20 @@ async function up(cmd, bin) {
6088
5971
  }
6089
5972
  if (!up)
6090
5973
  console.log(`it has not answered yet; \`fly logs -a ${cmd.name}\` shows what it is doing`);
6091
- printLinks(cmd.name, token);
5974
+ await printLinks(cmd.name, token);
6092
5975
  console.log("the volume holds the only copy of anything not pushed to a remote");
6093
- openUrl(launcherAddLink(`https://${cmd.name}.fly.dev`));
6094
5976
  }
6095
- function printLinks(app, token) {
5977
+ async function printLinks(app, token) {
5978
+ const origin = `https://${app}.fly.dev`;
5979
+ const handed = (await daemonFetch("/machines", { body: { origin, token } }))?.ok === true;
5980
+ if (handed) {
5981
+ console.log(`
5982
+ toyon: ${app}.fly.dev is in your list; open Toyon here to use it, and pair your phone from there`);
5983
+ return;
5984
+ }
6096
5985
  console.log(`
6097
- toyon: https://${app}.fly.dev/#token=${token}`);
5986
+ toyon: ${origin}/#token=${token}`);
6098
5987
  console.log("the link grants a shell on that machine; keep it to yourself");
6099
- console.log(`add it to your list at toyon.cloud: ${launcherAddLink(`https://${app}.fly.dev`)}`);
6100
5988
  }
6101
5989
  async function destroy(cmd, bin) {
6102
5990
  if (!cmd.yes) {
@@ -6114,7 +6002,7 @@ async function destroy(cmd, bin) {
6114
6002
  }
6115
6003
  }
6116
6004
  must(await capture(bin, ["apps", "destroy", cmd.name, "--yes"]), "could not delete the app");
6117
- rmSync2(tokenFile2(cmd.name), { force: true });
6005
+ rmSync(tokenFile2(cmd.name), { force: true });
6118
6006
  console.log(`deleted ${cmd.name}`);
6119
6007
  return 0;
6120
6008
  }
@@ -6133,7 +6021,7 @@ async function deploy(cmd) {
6133
6021
  else if (cmd.action === "destroy")
6134
6022
  return await destroy(cmd, bin);
6135
6023
  else if (existsSync5(tokenFile2(cmd.name))) {
6136
- printLinks(cmd.name, readFileSync4(tokenFile2(cmd.name), "utf8").trim());
6024
+ await printLinks(cmd.name, readFileSync4(tokenFile2(cmd.name), "utf8").trim());
6137
6025
  } else {
6138
6026
  throw new DeployError(`${cmd.name} was not deployed from this machine; its token is not in ${cloudDir}`);
6139
6027
  }
@@ -6318,6 +6206,41 @@ async function doctor() {
6318
6206
  return lines.every((l) => l.ok) ? 0 : 1;
6319
6207
  }
6320
6208
 
6209
+ // src/helper.ts
6210
+ import { fileURLToPath as fileURLToPath2 } from "url";
6211
+ function planHelper(args) {
6212
+ if (args.some((a) => a.startsWith(`${APP_SCHEME}:`)))
6213
+ return { kind: "start" };
6214
+ const paths = [];
6215
+ for (const a of args) {
6216
+ if (a.startsWith("file:")) {
6217
+ try {
6218
+ const p = fileURLToPath2(a);
6219
+ if (p !== "" && p !== "/")
6220
+ paths.push(p);
6221
+ } catch {}
6222
+ } else if (a !== "")
6223
+ paths.push(a);
6224
+ }
6225
+ return paths.length === 0 ? { kind: "start" } : { kind: "open", paths };
6226
+ }
6227
+ async function helper(args) {
6228
+ const plan = planHelper(args);
6229
+ console.log(`toyon helper: ${args.length > 0 ? args.join(" ") : "(nothing)"}`);
6230
+ const daemon = await ensureDaemon();
6231
+ if (!daemon)
6232
+ return 1;
6233
+ if (plan.kind === "start")
6234
+ return 0;
6235
+ for (const path of plan.paths) {
6236
+ const res = await post("/open", daemon.token, { path });
6237
+ if (!res.ok)
6238
+ console.error(`could not open ${path}: ${await res.text()}`);
6239
+ }
6240
+ showAppWindow(daemon);
6241
+ return 0;
6242
+ }
6243
+
6321
6244
  // src/logs.ts
6322
6245
  import { existsSync as existsSync7, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
6323
6246
  async function logs(opts) {
@@ -6353,1685 +6276,36 @@ async function logs(opts) {
6353
6276
  }
6354
6277
  }
6355
6278
 
6356
- // ../../node_modules/.bun/qrcode-generator@2.0.4/node_modules/qrcode-generator/dist/qrcode.mjs
6357
- var qrcode = function(typeNumber, errorCorrectionLevel) {
6358
- const PAD0 = 236;
6359
- const PAD1 = 17;
6360
- let _typeNumber = typeNumber;
6361
- const _errorCorrectionLevel = QRErrorCorrectionLevel[errorCorrectionLevel];
6362
- let _modules = null;
6363
- let _moduleCount = 0;
6364
- let _dataCache = null;
6365
- const _dataList = [];
6366
- const _this = {};
6367
- const makeImpl = function(test, maskPattern) {
6368
- _moduleCount = _typeNumber * 4 + 17;
6369
- _modules = function(moduleCount) {
6370
- const modules = new Array(moduleCount);
6371
- for (let row = 0;row < moduleCount; row += 1) {
6372
- modules[row] = new Array(moduleCount);
6373
- for (let col = 0;col < moduleCount; col += 1) {
6374
- modules[row][col] = null;
6375
- }
6376
- }
6377
- return modules;
6378
- }(_moduleCount);
6379
- setupPositionProbePattern(0, 0);
6380
- setupPositionProbePattern(_moduleCount - 7, 0);
6381
- setupPositionProbePattern(0, _moduleCount - 7);
6382
- setupPositionAdjustPattern();
6383
- setupTimingPattern();
6384
- setupTypeInfo(test, maskPattern);
6385
- if (_typeNumber >= 7) {
6386
- setupTypeNumber(test);
6387
- }
6388
- if (_dataCache == null) {
6389
- _dataCache = createData(_typeNumber, _errorCorrectionLevel, _dataList);
6390
- }
6391
- mapData(_dataCache, maskPattern);
6392
- };
6393
- const setupPositionProbePattern = function(row, col) {
6394
- for (let r = -1;r <= 7; r += 1) {
6395
- if (row + r <= -1 || _moduleCount <= row + r)
6396
- continue;
6397
- for (let c = -1;c <= 7; c += 1) {
6398
- if (col + c <= -1 || _moduleCount <= col + c)
6399
- continue;
6400
- if (0 <= r && r <= 6 && (c == 0 || c == 6) || 0 <= c && c <= 6 && (r == 0 || r == 6) || 2 <= r && r <= 4 && 2 <= c && c <= 4) {
6401
- _modules[row + r][col + c] = true;
6402
- } else {
6403
- _modules[row + r][col + c] = false;
6404
- }
6405
- }
6406
- }
6407
- };
6408
- const getBestMaskPattern = function() {
6409
- let minLostPoint = 0;
6410
- let pattern = 0;
6411
- for (let i = 0;i < 8; i += 1) {
6412
- makeImpl(true, i);
6413
- const lostPoint = QRUtil.getLostPoint(_this);
6414
- if (i == 0 || minLostPoint > lostPoint) {
6415
- minLostPoint = lostPoint;
6416
- pattern = i;
6417
- }
6418
- }
6419
- return pattern;
6420
- };
6421
- const setupTimingPattern = function() {
6422
- for (let r = 8;r < _moduleCount - 8; r += 1) {
6423
- if (_modules[r][6] != null) {
6424
- continue;
6425
- }
6426
- _modules[r][6] = r % 2 == 0;
6427
- }
6428
- for (let c = 8;c < _moduleCount - 8; c += 1) {
6429
- if (_modules[6][c] != null) {
6430
- continue;
6431
- }
6432
- _modules[6][c] = c % 2 == 0;
6433
- }
6434
- };
6435
- const setupPositionAdjustPattern = function() {
6436
- const pos = QRUtil.getPatternPosition(_typeNumber);
6437
- for (let i = 0;i < pos.length; i += 1) {
6438
- for (let j = 0;j < pos.length; j += 1) {
6439
- const row = pos[i];
6440
- const col = pos[j];
6441
- if (_modules[row][col] != null) {
6442
- continue;
6443
- }
6444
- for (let r = -2;r <= 2; r += 1) {
6445
- for (let c = -2;c <= 2; c += 1) {
6446
- if (r == -2 || r == 2 || c == -2 || c == 2 || r == 0 && c == 0) {
6447
- _modules[row + r][col + c] = true;
6448
- } else {
6449
- _modules[row + r][col + c] = false;
6450
- }
6451
- }
6452
- }
6453
- }
6454
- }
6455
- };
6456
- const setupTypeNumber = function(test) {
6457
- const bits = QRUtil.getBCHTypeNumber(_typeNumber);
6458
- for (let i = 0;i < 18; i += 1) {
6459
- const mod = !test && (bits >> i & 1) == 1;
6460
- _modules[Math.floor(i / 3)][i % 3 + _moduleCount - 8 - 3] = mod;
6461
- }
6462
- for (let i = 0;i < 18; i += 1) {
6463
- const mod = !test && (bits >> i & 1) == 1;
6464
- _modules[i % 3 + _moduleCount - 8 - 3][Math.floor(i / 3)] = mod;
6465
- }
6466
- };
6467
- const setupTypeInfo = function(test, maskPattern) {
6468
- const data = _errorCorrectionLevel << 3 | maskPattern;
6469
- const bits = QRUtil.getBCHTypeInfo(data);
6470
- for (let i = 0;i < 15; i += 1) {
6471
- const mod = !test && (bits >> i & 1) == 1;
6472
- if (i < 6) {
6473
- _modules[i][8] = mod;
6474
- } else if (i < 8) {
6475
- _modules[i + 1][8] = mod;
6476
- } else {
6477
- _modules[_moduleCount - 15 + i][8] = mod;
6478
- }
6479
- }
6480
- for (let i = 0;i < 15; i += 1) {
6481
- const mod = !test && (bits >> i & 1) == 1;
6482
- if (i < 8) {
6483
- _modules[8][_moduleCount - i - 1] = mod;
6484
- } else if (i < 9) {
6485
- _modules[8][15 - i - 1 + 1] = mod;
6486
- } else {
6487
- _modules[8][15 - i - 1] = mod;
6488
- }
6489
- }
6490
- _modules[_moduleCount - 8][8] = !test;
6491
- };
6492
- const mapData = function(data, maskPattern) {
6493
- let inc = -1;
6494
- let row = _moduleCount - 1;
6495
- let bitIndex = 7;
6496
- let byteIndex = 0;
6497
- const maskFunc = QRUtil.getMaskFunction(maskPattern);
6498
- for (let col = _moduleCount - 1;col > 0; col -= 2) {
6499
- if (col == 6)
6500
- col -= 1;
6501
- while (true) {
6502
- for (let c = 0;c < 2; c += 1) {
6503
- if (_modules[row][col - c] == null) {
6504
- let dark = false;
6505
- if (byteIndex < data.length) {
6506
- dark = (data[byteIndex] >>> bitIndex & 1) == 1;
6507
- }
6508
- const mask = maskFunc(row, col - c);
6509
- if (mask) {
6510
- dark = !dark;
6511
- }
6512
- _modules[row][col - c] = dark;
6513
- bitIndex -= 1;
6514
- if (bitIndex == -1) {
6515
- byteIndex += 1;
6516
- bitIndex = 7;
6517
- }
6518
- }
6519
- }
6520
- row += inc;
6521
- if (row < 0 || _moduleCount <= row) {
6522
- row -= inc;
6523
- inc = -inc;
6524
- break;
6525
- }
6526
- }
6527
- }
6528
- };
6529
- const createBytes = function(buffer, rsBlocks) {
6530
- let offset = 0;
6531
- let maxDcCount = 0;
6532
- let maxEcCount = 0;
6533
- const dcdata = new Array(rsBlocks.length);
6534
- const ecdata = new Array(rsBlocks.length);
6535
- for (let r = 0;r < rsBlocks.length; r += 1) {
6536
- const dcCount = rsBlocks[r].dataCount;
6537
- const ecCount = rsBlocks[r].totalCount - dcCount;
6538
- maxDcCount = Math.max(maxDcCount, dcCount);
6539
- maxEcCount = Math.max(maxEcCount, ecCount);
6540
- dcdata[r] = new Array(dcCount);
6541
- for (let i = 0;i < dcdata[r].length; i += 1) {
6542
- dcdata[r][i] = 255 & buffer.getBuffer()[i + offset];
6543
- }
6544
- offset += dcCount;
6545
- const rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount);
6546
- const rawPoly = qrPolynomial(dcdata[r], rsPoly.getLength() - 1);
6547
- const modPoly = rawPoly.mod(rsPoly);
6548
- ecdata[r] = new Array(rsPoly.getLength() - 1);
6549
- for (let i = 0;i < ecdata[r].length; i += 1) {
6550
- const modIndex = i + modPoly.getLength() - ecdata[r].length;
6551
- ecdata[r][i] = modIndex >= 0 ? modPoly.getAt(modIndex) : 0;
6552
- }
6553
- }
6554
- let totalCodeCount = 0;
6555
- for (let i = 0;i < rsBlocks.length; i += 1) {
6556
- totalCodeCount += rsBlocks[i].totalCount;
6557
- }
6558
- const data = new Array(totalCodeCount);
6559
- let index = 0;
6560
- for (let i = 0;i < maxDcCount; i += 1) {
6561
- for (let r = 0;r < rsBlocks.length; r += 1) {
6562
- if (i < dcdata[r].length) {
6563
- data[index] = dcdata[r][i];
6564
- index += 1;
6565
- }
6566
- }
6567
- }
6568
- for (let i = 0;i < maxEcCount; i += 1) {
6569
- for (let r = 0;r < rsBlocks.length; r += 1) {
6570
- if (i < ecdata[r].length) {
6571
- data[index] = ecdata[r][i];
6572
- index += 1;
6573
- }
6574
- }
6575
- }
6576
- return data;
6577
- };
6578
- const createData = function(typeNumber, errorCorrectionLevel, dataList) {
6579
- const rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectionLevel);
6580
- const buffer = qrBitBuffer();
6581
- for (let i = 0;i < dataList.length; i += 1) {
6582
- const data = dataList[i];
6583
- buffer.put(data.getMode(), 4);
6584
- buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber));
6585
- data.write(buffer);
6586
- }
6587
- let totalDataCount = 0;
6588
- for (let i = 0;i < rsBlocks.length; i += 1) {
6589
- totalDataCount += rsBlocks[i].dataCount;
6590
- }
6591
- if (buffer.getLengthInBits() > totalDataCount * 8) {
6592
- throw "code length overflow. (" + buffer.getLengthInBits() + ">" + totalDataCount * 8 + ")";
6593
- }
6594
- if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) {
6595
- buffer.put(0, 4);
6596
- }
6597
- while (buffer.getLengthInBits() % 8 != 0) {
6598
- buffer.putBit(false);
6599
- }
6600
- while (true) {
6601
- if (buffer.getLengthInBits() >= totalDataCount * 8) {
6602
- break;
6603
- }
6604
- buffer.put(PAD0, 8);
6605
- if (buffer.getLengthInBits() >= totalDataCount * 8) {
6606
- break;
6607
- }
6608
- buffer.put(PAD1, 8);
6609
- }
6610
- return createBytes(buffer, rsBlocks);
6611
- };
6612
- _this.addData = function(data, mode) {
6613
- mode = mode || "Byte";
6614
- let newData = null;
6615
- switch (mode) {
6616
- case "Numeric":
6617
- newData = qrNumber(data);
6618
- break;
6619
- case "Alphanumeric":
6620
- newData = qrAlphaNum(data);
6621
- break;
6622
- case "Byte":
6623
- newData = qr8BitByte(data);
6624
- break;
6625
- case "Kanji":
6626
- newData = qrKanji(data);
6627
- break;
6628
- default:
6629
- throw "mode:" + mode;
6630
- }
6631
- _dataList.push(newData);
6632
- _dataCache = null;
6633
- };
6634
- _this.isDark = function(row, col) {
6635
- if (row < 0 || _moduleCount <= row || col < 0 || _moduleCount <= col) {
6636
- throw row + "," + col;
6637
- }
6638
- return _modules[row][col];
6639
- };
6640
- _this.getModuleCount = function() {
6641
- return _moduleCount;
6642
- };
6643
- _this.make = function() {
6644
- if (_typeNumber < 1) {
6645
- let typeNumber = 1;
6646
- for (;typeNumber < 40; typeNumber++) {
6647
- const rsBlocks = QRRSBlock.getRSBlocks(typeNumber, _errorCorrectionLevel);
6648
- const buffer = qrBitBuffer();
6649
- for (let i = 0;i < _dataList.length; i++) {
6650
- const data = _dataList[i];
6651
- buffer.put(data.getMode(), 4);
6652
- buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber));
6653
- data.write(buffer);
6654
- }
6655
- let totalDataCount = 0;
6656
- for (let i = 0;i < rsBlocks.length; i++) {
6657
- totalDataCount += rsBlocks[i].dataCount;
6658
- }
6659
- if (buffer.getLengthInBits() <= totalDataCount * 8) {
6660
- break;
6661
- }
6662
- }
6663
- _typeNumber = typeNumber;
6664
- }
6665
- makeImpl(false, getBestMaskPattern());
6666
- };
6667
- _this.createTableTag = function(cellSize, margin) {
6668
- cellSize = cellSize || 2;
6669
- margin = typeof margin == "undefined" ? cellSize * 4 : margin;
6670
- let qrHtml = "";
6671
- qrHtml += '<table style="';
6672
- qrHtml += " border-width: 0px; border-style: none;";
6673
- qrHtml += " border-collapse: collapse;";
6674
- qrHtml += " padding: 0px; margin: " + margin + "px;";
6675
- qrHtml += '">';
6676
- qrHtml += "<tbody>";
6677
- for (let r = 0;r < _this.getModuleCount(); r += 1) {
6678
- qrHtml += "<tr>";
6679
- for (let c = 0;c < _this.getModuleCount(); c += 1) {
6680
- qrHtml += '<td style="';
6681
- qrHtml += " border-width: 0px; border-style: none;";
6682
- qrHtml += " border-collapse: collapse;";
6683
- qrHtml += " padding: 0px; margin: 0px;";
6684
- qrHtml += " width: " + cellSize + "px;";
6685
- qrHtml += " height: " + cellSize + "px;";
6686
- qrHtml += " background-color: ";
6687
- qrHtml += _this.isDark(r, c) ? "#000000" : "#ffffff";
6688
- qrHtml += ";";
6689
- qrHtml += '"/>';
6690
- }
6691
- qrHtml += "</tr>";
6692
- }
6693
- qrHtml += "</tbody>";
6694
- qrHtml += "</table>";
6695
- return qrHtml;
6696
- };
6697
- _this.createSvgTag = function(cellSize, margin, alt, title) {
6698
- let opts = {};
6699
- if (typeof arguments[0] == "object") {
6700
- opts = arguments[0];
6701
- cellSize = opts.cellSize;
6702
- margin = opts.margin;
6703
- alt = opts.alt;
6704
- title = opts.title;
6705
- }
6706
- cellSize = cellSize || 2;
6707
- margin = typeof margin == "undefined" ? cellSize * 4 : margin;
6708
- alt = typeof alt === "string" ? { text: alt } : alt || {};
6709
- alt.text = alt.text || null;
6710
- alt.id = alt.text ? alt.id || "qrcode-description" : null;
6711
- title = typeof title === "string" ? { text: title } : title || {};
6712
- title.text = title.text || null;
6713
- title.id = title.text ? title.id || "qrcode-title" : null;
6714
- const size = _this.getModuleCount() * cellSize + margin * 2;
6715
- let c, mc, r, mr, qrSvg = "", rect;
6716
- rect = "l" + cellSize + ",0 0," + cellSize + " -" + cellSize + ",0 0,-" + cellSize + "z ";
6717
- qrSvg += '<svg version="1.1" xmlns="http://www.w3.org/2000/svg"';
6718
- qrSvg += !opts.scalable ? ' width="' + size + 'px" height="' + size + 'px"' : "";
6719
- qrSvg += ' viewBox="0 0 ' + size + " " + size + '" ';
6720
- qrSvg += ' preserveAspectRatio="xMinYMin meet"';
6721
- qrSvg += title.text || alt.text ? ' role="img" aria-labelledby="' + escapeXml([title.id, alt.id].join(" ").trim()) + '"' : "";
6722
- qrSvg += ">";
6723
- qrSvg += title.text ? '<title id="' + escapeXml(title.id) + '">' + escapeXml(title.text) + "</title>" : "";
6724
- qrSvg += alt.text ? '<description id="' + escapeXml(alt.id) + '">' + escapeXml(alt.text) + "</description>" : "";
6725
- qrSvg += '<rect width="100%" height="100%" fill="white" cx="0" cy="0"/>';
6726
- qrSvg += '<path d="';
6727
- for (r = 0;r < _this.getModuleCount(); r += 1) {
6728
- mr = r * cellSize + margin;
6729
- for (c = 0;c < _this.getModuleCount(); c += 1) {
6730
- if (_this.isDark(r, c)) {
6731
- mc = c * cellSize + margin;
6732
- qrSvg += "M" + mc + "," + mr + rect;
6733
- }
6734
- }
6735
- }
6736
- qrSvg += '" stroke="transparent" fill="black"/>';
6737
- qrSvg += "</svg>";
6738
- return qrSvg;
6739
- };
6740
- _this.createDataURL = function(cellSize, margin) {
6741
- cellSize = cellSize || 2;
6742
- margin = typeof margin == "undefined" ? cellSize * 4 : margin;
6743
- const size = _this.getModuleCount() * cellSize + margin * 2;
6744
- const min = margin;
6745
- const max = size - margin;
6746
- return createDataURL(size, size, function(x, y) {
6747
- if (min <= x && x < max && min <= y && y < max) {
6748
- const c = Math.floor((x - min) / cellSize);
6749
- const r = Math.floor((y - min) / cellSize);
6750
- return _this.isDark(r, c) ? 0 : 1;
6751
- } else {
6752
- return 1;
6753
- }
6754
- });
6755
- };
6756
- _this.createImgTag = function(cellSize, margin, alt) {
6757
- cellSize = cellSize || 2;
6758
- margin = typeof margin == "undefined" ? cellSize * 4 : margin;
6759
- const size = _this.getModuleCount() * cellSize + margin * 2;
6760
- let img = "";
6761
- img += "<img";
6762
- img += ' src="';
6763
- img += _this.createDataURL(cellSize, margin);
6764
- img += '"';
6765
- img += ' width="';
6766
- img += size;
6767
- img += '"';
6768
- img += ' height="';
6769
- img += size;
6770
- img += '"';
6771
- if (alt) {
6772
- img += ' alt="';
6773
- img += escapeXml(alt);
6774
- img += '"';
6775
- }
6776
- img += "/>";
6777
- return img;
6778
- };
6779
- const escapeXml = function(s) {
6780
- let escaped = "";
6781
- for (let i = 0;i < s.length; i += 1) {
6782
- const c = s.charAt(i);
6783
- switch (c) {
6784
- case "<":
6785
- escaped += "&lt;";
6786
- break;
6787
- case ">":
6788
- escaped += "&gt;";
6789
- break;
6790
- case "&":
6791
- escaped += "&amp;";
6792
- break;
6793
- case '"':
6794
- escaped += "&quot;";
6795
- break;
6796
- default:
6797
- escaped += c;
6798
- break;
6799
- }
6800
- }
6801
- return escaped;
6802
- };
6803
- const _createHalfASCII = function(margin) {
6804
- const cellSize = 1;
6805
- margin = typeof margin == "undefined" ? cellSize * 2 : margin;
6806
- const size = _this.getModuleCount() * cellSize + margin * 2;
6807
- const min = margin;
6808
- const max = size - margin;
6809
- let y, x, r1, r2, p;
6810
- const blocks = {
6811
- "\u2588\u2588": "\u2588",
6812
- "\u2588 ": "\u2580",
6813
- " \u2588": "\u2584",
6814
- " ": " "
6815
- };
6816
- const blocksLastLineNoMargin = {
6817
- "\u2588\u2588": "\u2580",
6818
- "\u2588 ": "\u2580",
6819
- " \u2588": " ",
6820
- " ": " "
6821
- };
6822
- let ascii = "";
6823
- for (y = 0;y < size; y += 2) {
6824
- r1 = Math.floor((y - min) / cellSize);
6825
- r2 = Math.floor((y + 1 - min) / cellSize);
6826
- for (x = 0;x < size; x += 1) {
6827
- p = "\u2588";
6828
- if (min <= x && x < max && min <= y && y < max && _this.isDark(r1, Math.floor((x - min) / cellSize))) {
6829
- p = " ";
6830
- }
6831
- if (min <= x && x < max && min <= y + 1 && y + 1 < max && _this.isDark(r2, Math.floor((x - min) / cellSize))) {
6832
- p += " ";
6833
- } else {
6834
- p += "\u2588";
6835
- }
6836
- ascii += margin < 1 && y + 1 >= max ? blocksLastLineNoMargin[p] : blocks[p];
6837
- }
6838
- ascii += `
6839
- `;
6840
- }
6841
- if (size % 2 && margin > 0) {
6842
- return ascii.substring(0, ascii.length - size - 1) + Array(size + 1).join("\u2580");
6843
- }
6844
- return ascii.substring(0, ascii.length - 1);
6845
- };
6846
- _this.createASCII = function(cellSize, margin) {
6847
- cellSize = cellSize || 1;
6848
- if (cellSize < 2) {
6849
- return _createHalfASCII(margin);
6850
- }
6851
- cellSize -= 1;
6852
- margin = typeof margin == "undefined" ? cellSize * 2 : margin;
6853
- const size = _this.getModuleCount() * cellSize + margin * 2;
6854
- const min = margin;
6855
- const max = size - margin;
6856
- let y, x, r, p;
6857
- const white = Array(cellSize + 1).join("\u2588\u2588");
6858
- const black = Array(cellSize + 1).join(" ");
6859
- let ascii = "";
6860
- let line = "";
6861
- for (y = 0;y < size; y += 1) {
6862
- r = Math.floor((y - min) / cellSize);
6863
- line = "";
6864
- for (x = 0;x < size; x += 1) {
6865
- p = 1;
6866
- if (min <= x && x < max && min <= y && y < max && _this.isDark(r, Math.floor((x - min) / cellSize))) {
6867
- p = 0;
6868
- }
6869
- line += p ? white : black;
6870
- }
6871
- for (r = 0;r < cellSize; r += 1) {
6872
- ascii += line + `
6873
- `;
6874
- }
6875
- }
6876
- return ascii.substring(0, ascii.length - 1);
6877
- };
6878
- _this.renderTo2dContext = function(context, cellSize) {
6879
- cellSize = cellSize || 2;
6880
- const length = _this.getModuleCount();
6881
- for (let row = 0;row < length; row++) {
6882
- for (let col = 0;col < length; col++) {
6883
- context.fillStyle = _this.isDark(row, col) ? "black" : "white";
6884
- context.fillRect(col * cellSize, row * cellSize, cellSize, cellSize);
6885
- }
6886
- }
6887
- };
6888
- return _this;
6889
- };
6890
- qrcode.stringToBytes = function(s) {
6891
- const bytes = [];
6892
- for (let i = 0;i < s.length; i += 1) {
6893
- const c = s.charCodeAt(i);
6894
- bytes.push(c & 255);
6895
- }
6896
- return bytes;
6897
- };
6898
- qrcode.createStringToBytes = function(unicodeData, numChars) {
6899
- const unicodeMap = function() {
6900
- const bin = base64DecodeInputStream(unicodeData);
6901
- const read = function() {
6902
- const b = bin.read();
6903
- if (b == -1)
6904
- throw "eof";
6905
- return b;
6906
- };
6907
- let count = 0;
6908
- const unicodeMap = {};
6909
- while (true) {
6910
- const b0 = bin.read();
6911
- if (b0 == -1)
6912
- break;
6913
- const b1 = read();
6914
- const b2 = read();
6915
- const b3 = read();
6916
- const k = String.fromCharCode(b0 << 8 | b1);
6917
- const v = b2 << 8 | b3;
6918
- unicodeMap[k] = v;
6919
- count += 1;
6920
- }
6921
- if (count != numChars) {
6922
- throw count + " != " + numChars;
6923
- }
6924
- return unicodeMap;
6925
- }();
6926
- const unknownChar = 63;
6927
- return function(s) {
6928
- const bytes = [];
6929
- for (let i = 0;i < s.length; i += 1) {
6930
- const c = s.charCodeAt(i);
6931
- if (c < 128) {
6932
- bytes.push(c);
6933
- } else {
6934
- const b = unicodeMap[s.charAt(i)];
6935
- if (typeof b == "number") {
6936
- if ((b & 255) == b) {
6937
- bytes.push(b);
6938
- } else {
6939
- bytes.push(b >>> 8);
6940
- bytes.push(b & 255);
6941
- }
6942
- } else {
6943
- bytes.push(unknownChar);
6944
- }
6945
- }
6946
- }
6947
- return bytes;
6948
- };
6949
- };
6950
- var QRMode = {
6951
- MODE_NUMBER: 1 << 0,
6952
- MODE_ALPHA_NUM: 1 << 1,
6953
- MODE_8BIT_BYTE: 1 << 2,
6954
- MODE_KANJI: 1 << 3
6955
- };
6956
- var QRErrorCorrectionLevel = {
6957
- L: 1,
6958
- M: 0,
6959
- Q: 3,
6960
- H: 2
6961
- };
6962
- var QRMaskPattern = {
6963
- PATTERN000: 0,
6964
- PATTERN001: 1,
6965
- PATTERN010: 2,
6966
- PATTERN011: 3,
6967
- PATTERN100: 4,
6968
- PATTERN101: 5,
6969
- PATTERN110: 6,
6970
- PATTERN111: 7
6971
- };
6972
- var QRUtil = function() {
6973
- const PATTERN_POSITION_TABLE = [
6974
- [],
6975
- [6, 18],
6976
- [6, 22],
6977
- [6, 26],
6978
- [6, 30],
6979
- [6, 34],
6980
- [6, 22, 38],
6981
- [6, 24, 42],
6982
- [6, 26, 46],
6983
- [6, 28, 50],
6984
- [6, 30, 54],
6985
- [6, 32, 58],
6986
- [6, 34, 62],
6987
- [6, 26, 46, 66],
6988
- [6, 26, 48, 70],
6989
- [6, 26, 50, 74],
6990
- [6, 30, 54, 78],
6991
- [6, 30, 56, 82],
6992
- [6, 30, 58, 86],
6993
- [6, 34, 62, 90],
6994
- [6, 28, 50, 72, 94],
6995
- [6, 26, 50, 74, 98],
6996
- [6, 30, 54, 78, 102],
6997
- [6, 28, 54, 80, 106],
6998
- [6, 32, 58, 84, 110],
6999
- [6, 30, 58, 86, 114],
7000
- [6, 34, 62, 90, 118],
7001
- [6, 26, 50, 74, 98, 122],
7002
- [6, 30, 54, 78, 102, 126],
7003
- [6, 26, 52, 78, 104, 130],
7004
- [6, 30, 56, 82, 108, 134],
7005
- [6, 34, 60, 86, 112, 138],
7006
- [6, 30, 58, 86, 114, 142],
7007
- [6, 34, 62, 90, 118, 146],
7008
- [6, 30, 54, 78, 102, 126, 150],
7009
- [6, 24, 50, 76, 102, 128, 154],
7010
- [6, 28, 54, 80, 106, 132, 158],
7011
- [6, 32, 58, 84, 110, 136, 162],
7012
- [6, 26, 54, 82, 110, 138, 166],
7013
- [6, 30, 58, 86, 114, 142, 170]
7014
- ];
7015
- const G15 = 1 << 10 | 1 << 8 | 1 << 5 | 1 << 4 | 1 << 2 | 1 << 1 | 1 << 0;
7016
- const G18 = 1 << 12 | 1 << 11 | 1 << 10 | 1 << 9 | 1 << 8 | 1 << 5 | 1 << 2 | 1 << 0;
7017
- const G15_MASK = 1 << 14 | 1 << 12 | 1 << 10 | 1 << 4 | 1 << 1;
7018
- const _this = {};
7019
- const getBCHDigit = function(data) {
7020
- let digit = 0;
7021
- while (data != 0) {
7022
- digit += 1;
7023
- data >>>= 1;
7024
- }
7025
- return digit;
7026
- };
7027
- _this.getBCHTypeInfo = function(data) {
7028
- let d = data << 10;
7029
- while (getBCHDigit(d) - getBCHDigit(G15) >= 0) {
7030
- d ^= G15 << getBCHDigit(d) - getBCHDigit(G15);
7031
- }
7032
- return (data << 10 | d) ^ G15_MASK;
7033
- };
7034
- _this.getBCHTypeNumber = function(data) {
7035
- let d = data << 12;
7036
- while (getBCHDigit(d) - getBCHDigit(G18) >= 0) {
7037
- d ^= G18 << getBCHDigit(d) - getBCHDigit(G18);
7038
- }
7039
- return data << 12 | d;
7040
- };
7041
- _this.getPatternPosition = function(typeNumber) {
7042
- return PATTERN_POSITION_TABLE[typeNumber - 1];
7043
- };
7044
- _this.getMaskFunction = function(maskPattern) {
7045
- switch (maskPattern) {
7046
- case QRMaskPattern.PATTERN000:
7047
- return function(i, j) {
7048
- return (i + j) % 2 == 0;
7049
- };
7050
- case QRMaskPattern.PATTERN001:
7051
- return function(i, j) {
7052
- return i % 2 == 0;
7053
- };
7054
- case QRMaskPattern.PATTERN010:
7055
- return function(i, j) {
7056
- return j % 3 == 0;
7057
- };
7058
- case QRMaskPattern.PATTERN011:
7059
- return function(i, j) {
7060
- return (i + j) % 3 == 0;
7061
- };
7062
- case QRMaskPattern.PATTERN100:
7063
- return function(i, j) {
7064
- return (Math.floor(i / 2) + Math.floor(j / 3)) % 2 == 0;
7065
- };
7066
- case QRMaskPattern.PATTERN101:
7067
- return function(i, j) {
7068
- return i * j % 2 + i * j % 3 == 0;
7069
- };
7070
- case QRMaskPattern.PATTERN110:
7071
- return function(i, j) {
7072
- return (i * j % 2 + i * j % 3) % 2 == 0;
7073
- };
7074
- case QRMaskPattern.PATTERN111:
7075
- return function(i, j) {
7076
- return (i * j % 3 + (i + j) % 2) % 2 == 0;
7077
- };
7078
- default:
7079
- throw "bad maskPattern:" + maskPattern;
7080
- }
7081
- };
7082
- _this.getErrorCorrectPolynomial = function(errorCorrectLength) {
7083
- let a = qrPolynomial([1], 0);
7084
- for (let i = 0;i < errorCorrectLength; i += 1) {
7085
- a = a.multiply(qrPolynomial([1, QRMath.gexp(i)], 0));
7086
- }
7087
- return a;
7088
- };
7089
- _this.getLengthInBits = function(mode, type) {
7090
- if (1 <= type && type < 10) {
7091
- switch (mode) {
7092
- case QRMode.MODE_NUMBER:
7093
- return 10;
7094
- case QRMode.MODE_ALPHA_NUM:
7095
- return 9;
7096
- case QRMode.MODE_8BIT_BYTE:
7097
- return 8;
7098
- case QRMode.MODE_KANJI:
7099
- return 8;
7100
- default:
7101
- throw "mode:" + mode;
7102
- }
7103
- } else if (type < 27) {
7104
- switch (mode) {
7105
- case QRMode.MODE_NUMBER:
7106
- return 12;
7107
- case QRMode.MODE_ALPHA_NUM:
7108
- return 11;
7109
- case QRMode.MODE_8BIT_BYTE:
7110
- return 16;
7111
- case QRMode.MODE_KANJI:
7112
- return 10;
7113
- default:
7114
- throw "mode:" + mode;
7115
- }
7116
- } else if (type < 41) {
7117
- switch (mode) {
7118
- case QRMode.MODE_NUMBER:
7119
- return 14;
7120
- case QRMode.MODE_ALPHA_NUM:
7121
- return 13;
7122
- case QRMode.MODE_8BIT_BYTE:
7123
- return 16;
7124
- case QRMode.MODE_KANJI:
7125
- return 12;
7126
- default:
7127
- throw "mode:" + mode;
7128
- }
7129
- } else {
7130
- throw "type:" + type;
7131
- }
7132
- };
7133
- _this.getLostPoint = function(qrcode) {
7134
- const moduleCount = qrcode.getModuleCount();
7135
- let lostPoint = 0;
7136
- for (let row = 0;row < moduleCount; row += 1) {
7137
- for (let col = 0;col < moduleCount; col += 1) {
7138
- let sameCount = 0;
7139
- const dark = qrcode.isDark(row, col);
7140
- for (let r = -1;r <= 1; r += 1) {
7141
- if (row + r < 0 || moduleCount <= row + r) {
7142
- continue;
7143
- }
7144
- for (let c = -1;c <= 1; c += 1) {
7145
- if (col + c < 0 || moduleCount <= col + c) {
7146
- continue;
7147
- }
7148
- if (r == 0 && c == 0) {
7149
- continue;
7150
- }
7151
- if (dark == qrcode.isDark(row + r, col + c)) {
7152
- sameCount += 1;
7153
- }
7154
- }
7155
- }
7156
- if (sameCount > 5) {
7157
- lostPoint += 3 + sameCount - 5;
7158
- }
7159
- }
7160
- }
7161
- for (let row = 0;row < moduleCount - 1; row += 1) {
7162
- for (let col = 0;col < moduleCount - 1; col += 1) {
7163
- let count = 0;
7164
- if (qrcode.isDark(row, col))
7165
- count += 1;
7166
- if (qrcode.isDark(row + 1, col))
7167
- count += 1;
7168
- if (qrcode.isDark(row, col + 1))
7169
- count += 1;
7170
- if (qrcode.isDark(row + 1, col + 1))
7171
- count += 1;
7172
- if (count == 0 || count == 4) {
7173
- lostPoint += 3;
7174
- }
7175
- }
7176
- }
7177
- for (let row = 0;row < moduleCount; row += 1) {
7178
- for (let col = 0;col < moduleCount - 6; col += 1) {
7179
- if (qrcode.isDark(row, col) && !qrcode.isDark(row, col + 1) && qrcode.isDark(row, col + 2) && qrcode.isDark(row, col + 3) && qrcode.isDark(row, col + 4) && !qrcode.isDark(row, col + 5) && qrcode.isDark(row, col + 6)) {
7180
- lostPoint += 40;
7181
- }
7182
- }
7183
- }
7184
- for (let col = 0;col < moduleCount; col += 1) {
7185
- for (let row = 0;row < moduleCount - 6; row += 1) {
7186
- if (qrcode.isDark(row, col) && !qrcode.isDark(row + 1, col) && qrcode.isDark(row + 2, col) && qrcode.isDark(row + 3, col) && qrcode.isDark(row + 4, col) && !qrcode.isDark(row + 5, col) && qrcode.isDark(row + 6, col)) {
7187
- lostPoint += 40;
7188
- }
7189
- }
7190
- }
7191
- let darkCount = 0;
7192
- for (let col = 0;col < moduleCount; col += 1) {
7193
- for (let row = 0;row < moduleCount; row += 1) {
7194
- if (qrcode.isDark(row, col)) {
7195
- darkCount += 1;
7196
- }
7197
- }
7198
- }
7199
- const ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5;
7200
- lostPoint += ratio * 10;
7201
- return lostPoint;
7202
- };
7203
- return _this;
7204
- }();
7205
- var QRMath = function() {
7206
- const EXP_TABLE = new Array(256);
7207
- const LOG_TABLE = new Array(256);
7208
- for (let i = 0;i < 8; i += 1) {
7209
- EXP_TABLE[i] = 1 << i;
7210
- }
7211
- for (let i = 8;i < 256; i += 1) {
7212
- EXP_TABLE[i] = EXP_TABLE[i - 4] ^ EXP_TABLE[i - 5] ^ EXP_TABLE[i - 6] ^ EXP_TABLE[i - 8];
7213
- }
7214
- for (let i = 0;i < 255; i += 1) {
7215
- LOG_TABLE[EXP_TABLE[i]] = i;
7216
- }
7217
- const _this = {};
7218
- _this.glog = function(n) {
7219
- if (n < 1) {
7220
- throw "glog(" + n + ")";
7221
- }
7222
- return LOG_TABLE[n];
7223
- };
7224
- _this.gexp = function(n) {
7225
- while (n < 0) {
7226
- n += 255;
7227
- }
7228
- while (n >= 256) {
7229
- n -= 255;
7230
- }
7231
- return EXP_TABLE[n];
7232
- };
7233
- return _this;
7234
- }();
7235
- var qrPolynomial = function(num, shift) {
7236
- if (typeof num.length == "undefined") {
7237
- throw num.length + "/" + shift;
7238
- }
7239
- const _num = function() {
7240
- let offset = 0;
7241
- while (offset < num.length && num[offset] == 0) {
7242
- offset += 1;
7243
- }
7244
- const _num = new Array(num.length - offset + shift);
7245
- for (let i = 0;i < num.length - offset; i += 1) {
7246
- _num[i] = num[i + offset];
7247
- }
7248
- return _num;
7249
- }();
7250
- const _this = {};
7251
- _this.getAt = function(index) {
7252
- return _num[index];
7253
- };
7254
- _this.getLength = function() {
7255
- return _num.length;
7256
- };
7257
- _this.multiply = function(e) {
7258
- const num = new Array(_this.getLength() + e.getLength() - 1);
7259
- for (let i = 0;i < _this.getLength(); i += 1) {
7260
- for (let j = 0;j < e.getLength(); j += 1) {
7261
- num[i + j] ^= QRMath.gexp(QRMath.glog(_this.getAt(i)) + QRMath.glog(e.getAt(j)));
7262
- }
7263
- }
7264
- return qrPolynomial(num, 0);
7265
- };
7266
- _this.mod = function(e) {
7267
- if (_this.getLength() - e.getLength() < 0) {
7268
- return _this;
7269
- }
7270
- const ratio = QRMath.glog(_this.getAt(0)) - QRMath.glog(e.getAt(0));
7271
- const num = new Array(_this.getLength());
7272
- for (let i = 0;i < _this.getLength(); i += 1) {
7273
- num[i] = _this.getAt(i);
7274
- }
7275
- for (let i = 0;i < e.getLength(); i += 1) {
7276
- num[i] ^= QRMath.gexp(QRMath.glog(e.getAt(i)) + ratio);
7277
- }
7278
- return qrPolynomial(num, 0).mod(e);
7279
- };
7280
- return _this;
7281
- };
7282
- var QRRSBlock = function() {
7283
- const RS_BLOCK_TABLE = [
7284
- [1, 26, 19],
7285
- [1, 26, 16],
7286
- [1, 26, 13],
7287
- [1, 26, 9],
7288
- [1, 44, 34],
7289
- [1, 44, 28],
7290
- [1, 44, 22],
7291
- [1, 44, 16],
7292
- [1, 70, 55],
7293
- [1, 70, 44],
7294
- [2, 35, 17],
7295
- [2, 35, 13],
7296
- [1, 100, 80],
7297
- [2, 50, 32],
7298
- [2, 50, 24],
7299
- [4, 25, 9],
7300
- [1, 134, 108],
7301
- [2, 67, 43],
7302
- [2, 33, 15, 2, 34, 16],
7303
- [2, 33, 11, 2, 34, 12],
7304
- [2, 86, 68],
7305
- [4, 43, 27],
7306
- [4, 43, 19],
7307
- [4, 43, 15],
7308
- [2, 98, 78],
7309
- [4, 49, 31],
7310
- [2, 32, 14, 4, 33, 15],
7311
- [4, 39, 13, 1, 40, 14],
7312
- [2, 121, 97],
7313
- [2, 60, 38, 2, 61, 39],
7314
- [4, 40, 18, 2, 41, 19],
7315
- [4, 40, 14, 2, 41, 15],
7316
- [2, 146, 116],
7317
- [3, 58, 36, 2, 59, 37],
7318
- [4, 36, 16, 4, 37, 17],
7319
- [4, 36, 12, 4, 37, 13],
7320
- [2, 86, 68, 2, 87, 69],
7321
- [4, 69, 43, 1, 70, 44],
7322
- [6, 43, 19, 2, 44, 20],
7323
- [6, 43, 15, 2, 44, 16],
7324
- [4, 101, 81],
7325
- [1, 80, 50, 4, 81, 51],
7326
- [4, 50, 22, 4, 51, 23],
7327
- [3, 36, 12, 8, 37, 13],
7328
- [2, 116, 92, 2, 117, 93],
7329
- [6, 58, 36, 2, 59, 37],
7330
- [4, 46, 20, 6, 47, 21],
7331
- [7, 42, 14, 4, 43, 15],
7332
- [4, 133, 107],
7333
- [8, 59, 37, 1, 60, 38],
7334
- [8, 44, 20, 4, 45, 21],
7335
- [12, 33, 11, 4, 34, 12],
7336
- [3, 145, 115, 1, 146, 116],
7337
- [4, 64, 40, 5, 65, 41],
7338
- [11, 36, 16, 5, 37, 17],
7339
- [11, 36, 12, 5, 37, 13],
7340
- [5, 109, 87, 1, 110, 88],
7341
- [5, 65, 41, 5, 66, 42],
7342
- [5, 54, 24, 7, 55, 25],
7343
- [11, 36, 12, 7, 37, 13],
7344
- [5, 122, 98, 1, 123, 99],
7345
- [7, 73, 45, 3, 74, 46],
7346
- [15, 43, 19, 2, 44, 20],
7347
- [3, 45, 15, 13, 46, 16],
7348
- [1, 135, 107, 5, 136, 108],
7349
- [10, 74, 46, 1, 75, 47],
7350
- [1, 50, 22, 15, 51, 23],
7351
- [2, 42, 14, 17, 43, 15],
7352
- [5, 150, 120, 1, 151, 121],
7353
- [9, 69, 43, 4, 70, 44],
7354
- [17, 50, 22, 1, 51, 23],
7355
- [2, 42, 14, 19, 43, 15],
7356
- [3, 141, 113, 4, 142, 114],
7357
- [3, 70, 44, 11, 71, 45],
7358
- [17, 47, 21, 4, 48, 22],
7359
- [9, 39, 13, 16, 40, 14],
7360
- [3, 135, 107, 5, 136, 108],
7361
- [3, 67, 41, 13, 68, 42],
7362
- [15, 54, 24, 5, 55, 25],
7363
- [15, 43, 15, 10, 44, 16],
7364
- [4, 144, 116, 4, 145, 117],
7365
- [17, 68, 42],
7366
- [17, 50, 22, 6, 51, 23],
7367
- [19, 46, 16, 6, 47, 17],
7368
- [2, 139, 111, 7, 140, 112],
7369
- [17, 74, 46],
7370
- [7, 54, 24, 16, 55, 25],
7371
- [34, 37, 13],
7372
- [4, 151, 121, 5, 152, 122],
7373
- [4, 75, 47, 14, 76, 48],
7374
- [11, 54, 24, 14, 55, 25],
7375
- [16, 45, 15, 14, 46, 16],
7376
- [6, 147, 117, 4, 148, 118],
7377
- [6, 73, 45, 14, 74, 46],
7378
- [11, 54, 24, 16, 55, 25],
7379
- [30, 46, 16, 2, 47, 17],
7380
- [8, 132, 106, 4, 133, 107],
7381
- [8, 75, 47, 13, 76, 48],
7382
- [7, 54, 24, 22, 55, 25],
7383
- [22, 45, 15, 13, 46, 16],
7384
- [10, 142, 114, 2, 143, 115],
7385
- [19, 74, 46, 4, 75, 47],
7386
- [28, 50, 22, 6, 51, 23],
7387
- [33, 46, 16, 4, 47, 17],
7388
- [8, 152, 122, 4, 153, 123],
7389
- [22, 73, 45, 3, 74, 46],
7390
- [8, 53, 23, 26, 54, 24],
7391
- [12, 45, 15, 28, 46, 16],
7392
- [3, 147, 117, 10, 148, 118],
7393
- [3, 73, 45, 23, 74, 46],
7394
- [4, 54, 24, 31, 55, 25],
7395
- [11, 45, 15, 31, 46, 16],
7396
- [7, 146, 116, 7, 147, 117],
7397
- [21, 73, 45, 7, 74, 46],
7398
- [1, 53, 23, 37, 54, 24],
7399
- [19, 45, 15, 26, 46, 16],
7400
- [5, 145, 115, 10, 146, 116],
7401
- [19, 75, 47, 10, 76, 48],
7402
- [15, 54, 24, 25, 55, 25],
7403
- [23, 45, 15, 25, 46, 16],
7404
- [13, 145, 115, 3, 146, 116],
7405
- [2, 74, 46, 29, 75, 47],
7406
- [42, 54, 24, 1, 55, 25],
7407
- [23, 45, 15, 28, 46, 16],
7408
- [17, 145, 115],
7409
- [10, 74, 46, 23, 75, 47],
7410
- [10, 54, 24, 35, 55, 25],
7411
- [19, 45, 15, 35, 46, 16],
7412
- [17, 145, 115, 1, 146, 116],
7413
- [14, 74, 46, 21, 75, 47],
7414
- [29, 54, 24, 19, 55, 25],
7415
- [11, 45, 15, 46, 46, 16],
7416
- [13, 145, 115, 6, 146, 116],
7417
- [14, 74, 46, 23, 75, 47],
7418
- [44, 54, 24, 7, 55, 25],
7419
- [59, 46, 16, 1, 47, 17],
7420
- [12, 151, 121, 7, 152, 122],
7421
- [12, 75, 47, 26, 76, 48],
7422
- [39, 54, 24, 14, 55, 25],
7423
- [22, 45, 15, 41, 46, 16],
7424
- [6, 151, 121, 14, 152, 122],
7425
- [6, 75, 47, 34, 76, 48],
7426
- [46, 54, 24, 10, 55, 25],
7427
- [2, 45, 15, 64, 46, 16],
7428
- [17, 152, 122, 4, 153, 123],
7429
- [29, 74, 46, 14, 75, 47],
7430
- [49, 54, 24, 10, 55, 25],
7431
- [24, 45, 15, 46, 46, 16],
7432
- [4, 152, 122, 18, 153, 123],
7433
- [13, 74, 46, 32, 75, 47],
7434
- [48, 54, 24, 14, 55, 25],
7435
- [42, 45, 15, 32, 46, 16],
7436
- [20, 147, 117, 4, 148, 118],
7437
- [40, 75, 47, 7, 76, 48],
7438
- [43, 54, 24, 22, 55, 25],
7439
- [10, 45, 15, 67, 46, 16],
7440
- [19, 148, 118, 6, 149, 119],
7441
- [18, 75, 47, 31, 76, 48],
7442
- [34, 54, 24, 34, 55, 25],
7443
- [20, 45, 15, 61, 46, 16]
7444
- ];
7445
- const qrRSBlock = function(totalCount, dataCount) {
7446
- const _this = {};
7447
- _this.totalCount = totalCount;
7448
- _this.dataCount = dataCount;
7449
- return _this;
7450
- };
7451
- const _this = {};
7452
- const getRsBlockTable = function(typeNumber, errorCorrectionLevel) {
7453
- switch (errorCorrectionLevel) {
7454
- case QRErrorCorrectionLevel.L:
7455
- return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0];
7456
- case QRErrorCorrectionLevel.M:
7457
- return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1];
7458
- case QRErrorCorrectionLevel.Q:
7459
- return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2];
7460
- case QRErrorCorrectionLevel.H:
7461
- return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3];
7462
- default:
7463
- return;
7464
- }
7465
- };
7466
- _this.getRSBlocks = function(typeNumber, errorCorrectionLevel) {
7467
- const rsBlock = getRsBlockTable(typeNumber, errorCorrectionLevel);
7468
- if (typeof rsBlock == "undefined") {
7469
- throw "bad rs block @ typeNumber:" + typeNumber + "/errorCorrectionLevel:" + errorCorrectionLevel;
7470
- }
7471
- const length = rsBlock.length / 3;
7472
- const list = [];
7473
- for (let i = 0;i < length; i += 1) {
7474
- const count = rsBlock[i * 3 + 0];
7475
- const totalCount = rsBlock[i * 3 + 1];
7476
- const dataCount = rsBlock[i * 3 + 2];
7477
- for (let j = 0;j < count; j += 1) {
7478
- list.push(qrRSBlock(totalCount, dataCount));
7479
- }
7480
- }
7481
- return list;
7482
- };
7483
- return _this;
7484
- }();
7485
- var qrBitBuffer = function() {
7486
- const _buffer = [];
7487
- let _length = 0;
7488
- const _this = {};
7489
- _this.getBuffer = function() {
7490
- return _buffer;
7491
- };
7492
- _this.getAt = function(index) {
7493
- const bufIndex = Math.floor(index / 8);
7494
- return (_buffer[bufIndex] >>> 7 - index % 8 & 1) == 1;
7495
- };
7496
- _this.put = function(num, length) {
7497
- for (let i = 0;i < length; i += 1) {
7498
- _this.putBit((num >>> length - i - 1 & 1) == 1);
7499
- }
7500
- };
7501
- _this.getLengthInBits = function() {
7502
- return _length;
7503
- };
7504
- _this.putBit = function(bit) {
7505
- const bufIndex = Math.floor(_length / 8);
7506
- if (_buffer.length <= bufIndex) {
7507
- _buffer.push(0);
7508
- }
7509
- if (bit) {
7510
- _buffer[bufIndex] |= 128 >>> _length % 8;
7511
- }
7512
- _length += 1;
7513
- };
7514
- return _this;
7515
- };
7516
- var qrNumber = function(data) {
7517
- const _mode = QRMode.MODE_NUMBER;
7518
- const _data = data;
7519
- const _this = {};
7520
- _this.getMode = function() {
7521
- return _mode;
7522
- };
7523
- _this.getLength = function(buffer) {
7524
- return _data.length;
7525
- };
7526
- _this.write = function(buffer) {
7527
- const data = _data;
7528
- let i = 0;
7529
- while (i + 2 < data.length) {
7530
- buffer.put(strToNum(data.substring(i, i + 3)), 10);
7531
- i += 3;
7532
- }
7533
- if (i < data.length) {
7534
- if (data.length - i == 1) {
7535
- buffer.put(strToNum(data.substring(i, i + 1)), 4);
7536
- } else if (data.length - i == 2) {
7537
- buffer.put(strToNum(data.substring(i, i + 2)), 7);
7538
- }
7539
- }
7540
- };
7541
- const strToNum = function(s) {
7542
- let num = 0;
7543
- for (let i = 0;i < s.length; i += 1) {
7544
- num = num * 10 + chatToNum(s.charAt(i));
7545
- }
7546
- return num;
7547
- };
7548
- const chatToNum = function(c) {
7549
- if ("0" <= c && c <= "9") {
7550
- return c.charCodeAt(0) - 48;
7551
- }
7552
- throw "illegal char :" + c;
7553
- };
7554
- return _this;
7555
- };
7556
- var qrAlphaNum = function(data) {
7557
- const _mode = QRMode.MODE_ALPHA_NUM;
7558
- const _data = data;
7559
- const _this = {};
7560
- _this.getMode = function() {
7561
- return _mode;
7562
- };
7563
- _this.getLength = function(buffer) {
7564
- return _data.length;
7565
- };
7566
- _this.write = function(buffer) {
7567
- const s = _data;
7568
- let i = 0;
7569
- while (i + 1 < s.length) {
7570
- buffer.put(getCode(s.charAt(i)) * 45 + getCode(s.charAt(i + 1)), 11);
7571
- i += 2;
7572
- }
7573
- if (i < s.length) {
7574
- buffer.put(getCode(s.charAt(i)), 6);
7575
- }
7576
- };
7577
- const getCode = function(c) {
7578
- if ("0" <= c && c <= "9") {
7579
- return c.charCodeAt(0) - 48;
7580
- } else if ("A" <= c && c <= "Z") {
7581
- return c.charCodeAt(0) - 65 + 10;
7582
- } else {
7583
- switch (c) {
7584
- case " ":
7585
- return 36;
7586
- case "$":
7587
- return 37;
7588
- case "%":
7589
- return 38;
7590
- case "*":
7591
- return 39;
7592
- case "+":
7593
- return 40;
7594
- case "-":
7595
- return 41;
7596
- case ".":
7597
- return 42;
7598
- case "/":
7599
- return 43;
7600
- case ":":
7601
- return 44;
7602
- default:
7603
- throw "illegal char :" + c;
7604
- }
7605
- }
7606
- };
7607
- return _this;
7608
- };
7609
- var qr8BitByte = function(data) {
7610
- const _mode = QRMode.MODE_8BIT_BYTE;
7611
- const _data = data;
7612
- const _bytes = qrcode.stringToBytes(data);
7613
- const _this = {};
7614
- _this.getMode = function() {
7615
- return _mode;
7616
- };
7617
- _this.getLength = function(buffer) {
7618
- return _bytes.length;
7619
- };
7620
- _this.write = function(buffer) {
7621
- for (let i = 0;i < _bytes.length; i += 1) {
7622
- buffer.put(_bytes[i], 8);
7623
- }
7624
- };
7625
- return _this;
7626
- };
7627
- var qrKanji = function(data) {
7628
- const _mode = QRMode.MODE_KANJI;
7629
- const _data = data;
7630
- const stringToBytes = qrcode.stringToBytes;
7631
- (function(c, code) {
7632
- const test = stringToBytes(c);
7633
- if (test.length != 2 || (test[0] << 8 | test[1]) != code) {
7634
- throw "sjis not supported.";
7635
- }
7636
- })("\u53CB", 38726);
7637
- const _bytes = stringToBytes(data);
7638
- const _this = {};
7639
- _this.getMode = function() {
7640
- return _mode;
7641
- };
7642
- _this.getLength = function(buffer) {
7643
- return ~~(_bytes.length / 2);
7644
- };
7645
- _this.write = function(buffer) {
7646
- const data = _bytes;
7647
- let i = 0;
7648
- while (i + 1 < data.length) {
7649
- let c = (255 & data[i]) << 8 | 255 & data[i + 1];
7650
- if (33088 <= c && c <= 40956) {
7651
- c -= 33088;
7652
- } else if (57408 <= c && c <= 60351) {
7653
- c -= 49472;
7654
- } else {
7655
- throw "illegal char at " + (i + 1) + "/" + c;
7656
- }
7657
- c = (c >>> 8 & 255) * 192 + (c & 255);
7658
- buffer.put(c, 13);
7659
- i += 2;
7660
- }
7661
- if (i < data.length) {
7662
- throw "illegal char at " + (i + 1);
7663
- }
7664
- };
7665
- return _this;
7666
- };
7667
- var byteArrayOutputStream = function() {
7668
- const _bytes = [];
7669
- const _this = {};
7670
- _this.writeByte = function(b) {
7671
- _bytes.push(b & 255);
7672
- };
7673
- _this.writeShort = function(i) {
7674
- _this.writeByte(i);
7675
- _this.writeByte(i >>> 8);
7676
- };
7677
- _this.writeBytes = function(b, off, len) {
7678
- off = off || 0;
7679
- len = len || b.length;
7680
- for (let i = 0;i < len; i += 1) {
7681
- _this.writeByte(b[i + off]);
7682
- }
7683
- };
7684
- _this.writeString = function(s) {
7685
- for (let i = 0;i < s.length; i += 1) {
7686
- _this.writeByte(s.charCodeAt(i));
7687
- }
7688
- };
7689
- _this.toByteArray = function() {
7690
- return _bytes;
7691
- };
7692
- _this.toString = function() {
7693
- let s = "";
7694
- s += "[";
7695
- for (let i = 0;i < _bytes.length; i += 1) {
7696
- if (i > 0) {
7697
- s += ",";
7698
- }
7699
- s += _bytes[i];
7700
- }
7701
- s += "]";
7702
- return s;
7703
- };
7704
- return _this;
7705
- };
7706
- var base64EncodeOutputStream = function() {
7707
- let _buffer = 0;
7708
- let _buflen = 0;
7709
- let _length = 0;
7710
- let _base64 = "";
7711
- const _this = {};
7712
- const writeEncoded = function(b) {
7713
- _base64 += String.fromCharCode(encode(b & 63));
7714
- };
7715
- const encode = function(n) {
7716
- if (n < 0) {
7717
- throw "n:" + n;
7718
- } else if (n < 26) {
7719
- return 65 + n;
7720
- } else if (n < 52) {
7721
- return 97 + (n - 26);
7722
- } else if (n < 62) {
7723
- return 48 + (n - 52);
7724
- } else if (n == 62) {
7725
- return 43;
7726
- } else if (n == 63) {
7727
- return 47;
7728
- } else {
7729
- throw "n:" + n;
7730
- }
7731
- };
7732
- _this.writeByte = function(n) {
7733
- _buffer = _buffer << 8 | n & 255;
7734
- _buflen += 8;
7735
- _length += 1;
7736
- while (_buflen >= 6) {
7737
- writeEncoded(_buffer >>> _buflen - 6);
7738
- _buflen -= 6;
7739
- }
7740
- };
7741
- _this.flush = function() {
7742
- if (_buflen > 0) {
7743
- writeEncoded(_buffer << 6 - _buflen);
7744
- _buffer = 0;
7745
- _buflen = 0;
7746
- }
7747
- if (_length % 3 != 0) {
7748
- const padlen = 3 - _length % 3;
7749
- for (let i = 0;i < padlen; i += 1) {
7750
- _base64 += "=";
7751
- }
7752
- }
7753
- };
7754
- _this.toString = function() {
7755
- return _base64;
7756
- };
7757
- return _this;
7758
- };
7759
- var base64DecodeInputStream = function(str) {
7760
- const _str = str;
7761
- let _pos = 0;
7762
- let _buffer = 0;
7763
- let _buflen = 0;
7764
- const _this = {};
7765
- _this.read = function() {
7766
- while (_buflen < 8) {
7767
- if (_pos >= _str.length) {
7768
- if (_buflen == 0) {
7769
- return -1;
7770
- }
7771
- throw "unexpected end of file./" + _buflen;
7772
- }
7773
- const c = _str.charAt(_pos);
7774
- _pos += 1;
7775
- if (c == "=") {
7776
- _buflen = 0;
7777
- return -1;
7778
- } else if (c.match(/^\s$/)) {
7779
- continue;
7780
- }
7781
- _buffer = _buffer << 6 | decode(c.charCodeAt(0));
7782
- _buflen += 6;
7783
- }
7784
- const n = _buffer >>> _buflen - 8 & 255;
7785
- _buflen -= 8;
7786
- return n;
7787
- };
7788
- const decode = function(c) {
7789
- if (65 <= c && c <= 90) {
7790
- return c - 65;
7791
- } else if (97 <= c && c <= 122) {
7792
- return c - 97 + 26;
7793
- } else if (48 <= c && c <= 57) {
7794
- return c - 48 + 52;
7795
- } else if (c == 43) {
7796
- return 62;
7797
- } else if (c == 47) {
7798
- return 63;
7799
- } else {
7800
- throw "c:" + c;
7801
- }
7802
- };
7803
- return _this;
7804
- };
7805
- var gifImage = function(width, height) {
7806
- const _width = width;
7807
- const _height = height;
7808
- const _data = new Array(width * height);
7809
- const _this = {};
7810
- _this.setPixel = function(x, y, pixel) {
7811
- _data[y * _width + x] = pixel;
7812
- };
7813
- _this.write = function(out) {
7814
- out.writeString("GIF87a");
7815
- out.writeShort(_width);
7816
- out.writeShort(_height);
7817
- out.writeByte(128);
7818
- out.writeByte(0);
7819
- out.writeByte(0);
7820
- out.writeByte(0);
7821
- out.writeByte(0);
7822
- out.writeByte(0);
7823
- out.writeByte(255);
7824
- out.writeByte(255);
7825
- out.writeByte(255);
7826
- out.writeString(",");
7827
- out.writeShort(0);
7828
- out.writeShort(0);
7829
- out.writeShort(_width);
7830
- out.writeShort(_height);
7831
- out.writeByte(0);
7832
- const lzwMinCodeSize = 2;
7833
- const raster = getLZWRaster(lzwMinCodeSize);
7834
- out.writeByte(lzwMinCodeSize);
7835
- let offset = 0;
7836
- while (raster.length - offset > 255) {
7837
- out.writeByte(255);
7838
- out.writeBytes(raster, offset, 255);
7839
- offset += 255;
7840
- }
7841
- out.writeByte(raster.length - offset);
7842
- out.writeBytes(raster, offset, raster.length - offset);
7843
- out.writeByte(0);
7844
- out.writeString(";");
7845
- };
7846
- const bitOutputStream = function(out) {
7847
- const _out = out;
7848
- let _bitLength = 0;
7849
- let _bitBuffer = 0;
7850
- const _this = {};
7851
- _this.write = function(data, length) {
7852
- if (data >>> length != 0) {
7853
- throw "length over";
7854
- }
7855
- while (_bitLength + length >= 8) {
7856
- _out.writeByte(255 & (data << _bitLength | _bitBuffer));
7857
- length -= 8 - _bitLength;
7858
- data >>>= 8 - _bitLength;
7859
- _bitBuffer = 0;
7860
- _bitLength = 0;
7861
- }
7862
- _bitBuffer = data << _bitLength | _bitBuffer;
7863
- _bitLength = _bitLength + length;
7864
- };
7865
- _this.flush = function() {
7866
- if (_bitLength > 0) {
7867
- _out.writeByte(_bitBuffer);
7868
- }
7869
- };
7870
- return _this;
7871
- };
7872
- const getLZWRaster = function(lzwMinCodeSize) {
7873
- const clearCode = 1 << lzwMinCodeSize;
7874
- const endCode = (1 << lzwMinCodeSize) + 1;
7875
- let bitLength = lzwMinCodeSize + 1;
7876
- const table = lzwTable();
7877
- for (let i = 0;i < clearCode; i += 1) {
7878
- table.add(String.fromCharCode(i));
7879
- }
7880
- table.add(String.fromCharCode(clearCode));
7881
- table.add(String.fromCharCode(endCode));
7882
- const byteOut = byteArrayOutputStream();
7883
- const bitOut = bitOutputStream(byteOut);
7884
- bitOut.write(clearCode, bitLength);
7885
- let dataIndex = 0;
7886
- let s = String.fromCharCode(_data[dataIndex]);
7887
- dataIndex += 1;
7888
- while (dataIndex < _data.length) {
7889
- const c = String.fromCharCode(_data[dataIndex]);
7890
- dataIndex += 1;
7891
- if (table.contains(s + c)) {
7892
- s = s + c;
7893
- } else {
7894
- bitOut.write(table.indexOf(s), bitLength);
7895
- if (table.size() < 4095) {
7896
- if (table.size() == 1 << bitLength) {
7897
- bitLength += 1;
7898
- }
7899
- table.add(s + c);
7900
- }
7901
- s = c;
7902
- }
7903
- }
7904
- bitOut.write(table.indexOf(s), bitLength);
7905
- bitOut.write(endCode, bitLength);
7906
- bitOut.flush();
7907
- return byteOut.toByteArray();
7908
- };
7909
- const lzwTable = function() {
7910
- const _map = {};
7911
- let _size = 0;
7912
- const _this = {};
7913
- _this.add = function(key) {
7914
- if (_this.contains(key)) {
7915
- throw "dup key:" + key;
7916
- }
7917
- _map[key] = _size;
7918
- _size += 1;
7919
- };
7920
- _this.size = function() {
7921
- return _size;
7922
- };
7923
- _this.indexOf = function(key) {
7924
- return _map[key];
7925
- };
7926
- _this.contains = function(key) {
7927
- return typeof _map[key] != "undefined";
7928
- };
7929
- return _this;
7930
- };
7931
- return _this;
7932
- };
7933
- var createDataURL = function(width, height, getPixel) {
7934
- const gif = gifImage(width, height);
7935
- for (let y = 0;y < height; y += 1) {
7936
- for (let x = 0;x < width; x += 1) {
7937
- gif.setPixel(x, y, getPixel(x, y));
7938
- }
7939
- }
7940
- const b = byteArrayOutputStream();
7941
- gif.write(b);
7942
- const base64 = base64EncodeOutputStream();
7943
- const bytes = b.toByteArray();
7944
- for (let i = 0;i < bytes.length; i += 1) {
7945
- base64.writeByte(bytes[i]);
7946
- }
7947
- base64.flush();
7948
- return "data:image/gif;base64," + base64;
7949
- };
7950
- var qrcode_default = qrcode;
7951
- var stringToBytes = qrcode.stringToBytes;
7952
-
7953
- // ../shared/src/qr.ts
7954
- function qrModules(text) {
7955
- const qr = qrcode_default(0, "M");
7956
- qr.addData(text, "Byte");
7957
- qr.make();
7958
- const n = qr.getModuleCount();
7959
- return Array.from({ length: n }, (_, r) => Array.from({ length: n }, (_, c) => qr.isDark(r, c)));
7960
- }
7961
- function qrText(modules, margin = 2) {
7962
- const n = modules.length + margin * 2;
7963
- const light = (r, c) => {
7964
- const row = modules[r - margin];
7965
- const v = row?.[c - margin];
7966
- return v === undefined ? true : !v;
7967
- };
7968
- const lines = [];
7969
- for (let r = 0;r < n; r += 2) {
7970
- let line = "";
7971
- for (let c = 0;c < n; c++) {
7972
- const top = light(r, c);
7973
- const bottom = r + 1 < n ? light(r + 1, c) : true;
7974
- line += top && bottom ? "\u2588" : top ? "\u2580" : bottom ? "\u2584" : " ";
7975
- }
7976
- lines.push(line);
7977
- }
7978
- return lines.join(`
7979
- `);
7980
- }
7981
-
7982
- // src/pair.ts
7983
- async function printPairCode() {
7984
- const token = readToken();
7985
- if (!token)
7986
- return "daemon token missing; `toyon doctor` says where it looked";
7987
- let res;
7988
- try {
7989
- res = await fetch(`${base}/pair`, { method: "POST", headers: { authorization: `Bearer ${token}` } });
7990
- } catch {
7991
- return "Toyon is not running. Start it with `toyon`, then run `toyon pair`.";
7992
- }
7993
- if (!res.ok)
7994
- return await res.text() || `the daemon refused a pairing code (${res.status})`;
7995
- const mint = await res.json();
7996
- console.log(`
7997
- ${qrText(qrModules(mint.url))}
7998
- `);
7999
- console.log("Scan with your phone's camera. The code works once and lasts 2 minutes.");
8000
- console.log(mint.url);
8001
- if (isTailnetName(new URL(mint.url).hostname)) {
8002
- const r = await fetch(`${base}/pair/phones`, { headers: { authorization: `Bearer ${token}` } }).catch(() => null);
8003
- const phones = r?.ok ? await r.json() : null;
8004
- console.log(tailnetLine(phones).text);
8005
- }
8006
- return null;
8007
- }
8008
- async function pair() {
8009
- if (!await health()) {
8010
- console.error("toyon: Toyon is not running. Start it with `toyon`, then run `toyon pair`.");
8011
- return 1;
8012
- }
8013
- const refused = await printPairCode();
8014
- if (refused) {
8015
- console.error(`toyon: ${refused}`);
8016
- return 1;
8017
- }
8018
- return 0;
8019
- }
8020
-
8021
- // src/remote.ts
8022
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
8023
-
8024
- // src/tailscale.ts
8025
- class TailscaleError extends Error {
8026
- }
8027
- function tailscaleCli(bin = Bun.which("tailscale")) {
8028
- return async (args) => {
8029
- if (bin === null) {
8030
- throw new TailscaleError("Tailscale is not installed here (no tailscale on PATH); https://tailscale.com/download");
8031
- }
8032
- const p = Bun.spawn([bin, ...args], { stdin: "ignore", stdout: "pipe", stderr: "pipe" });
8033
- const [out, err] = await Promise.all([new Response(p.stdout).text(), new Response(p.stderr).text()]);
8034
- return { ok: await p.exited === 0, out, err };
6279
+ // src/prompt.ts
6280
+ function confirm(question) {
6281
+ process.stdout.write(`${question} [y/N] `);
6282
+ return new Promise((resolve) => {
6283
+ process.stdin.once("data", (chunk) => resolve(String(chunk).trim().toLowerCase().startsWith("y")));
6284
+ process.stdin.once("end", () => resolve(false));
6285
+ });
6286
+ }
6287
+
6288
+ // src/remote.ts
6289
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
6290
+
6291
+ // ../shared/src/tailscale.ts
6292
+ import { existsSync as existsSync8, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
6293
+ var dnsName = (raw) => (raw ?? "").replace(/\.$/, "").toLowerCase();
6294
+
6295
+ class TailscaleError extends Error {
6296
+ }
6297
+ var MAC_APP_CLI = "/Applications/Tailscale.app/Contents/MacOS/Tailscale";
6298
+ function findTailscale() {
6299
+ return Bun.which("tailscale") ?? (existsSync8(MAC_APP_CLI) ? MAC_APP_CLI : null);
6300
+ }
6301
+ var NOT_INSTALLED = "Tailscale is not installed here (no tailscale on PATH); https://tailscale.com/download";
6302
+ function tailscaleCli(bin = findTailscale()) {
6303
+ return async (args) => {
6304
+ if (bin === null)
6305
+ throw new TailscaleError(NOT_INSTALLED);
6306
+ const p = Bun.spawn([bin, ...args], { stdin: "ignore", stdout: "pipe", stderr: "pipe", timeout: 5000 });
6307
+ const [out, err] = await Promise.all([new Response(p.stdout).text(), new Response(p.stderr).text()]);
6308
+ return { ok: await p.exited === 0, out, err };
8035
6309
  };
8036
6310
  }
8037
6311
  function entries(daemonPort) {
@@ -8046,33 +6320,66 @@ var lastLine = (r) => (r.err.trim() || r.out.trim()).split(`
8046
6320
  `).at(-1) ?? "tailscale failed";
8047
6321
  var notRunning = (text) => /failed to connect|is Tailscale running/i.test(text);
8048
6322
  var NOT_RUNNING = "Tailscale is not running here; start the Tailscale app or the tailscaled service, then run `tailscale up`";
8049
- function tailnetName(json) {
6323
+ function readinessOf(json) {
8050
6324
  let st;
8051
6325
  try {
8052
6326
  st = JSON.parse(json);
8053
6327
  } catch {
8054
- throw new TailscaleError(notRunning(json) ? NOT_RUNNING : "tailscale status --json printed nothing Toyon can read");
6328
+ return {
6329
+ state: "not-running",
6330
+ name: null,
6331
+ line: notRunning(json) ? NOT_RUNNING : "tailscale status --json printed nothing Toyon can read"
6332
+ };
8055
6333
  }
6334
+ const name = dnsName(st.Self?.DNSName) || null;
8056
6335
  switch (st.BackendState) {
8057
6336
  case "Running":
8058
6337
  break;
8059
6338
  case "NeedsLogin":
8060
- throw new TailscaleError("Tailscale is signed out; run `tailscale up`, then this again");
6339
+ return { state: "signed-out", name, line: "Tailscale is signed out; run `tailscale up`, then this again" };
8061
6340
  case "NeedsMachineAuth":
8062
- throw new TailscaleError("this machine is waiting to be approved in the Tailscale admin console");
6341
+ return {
6342
+ state: "needs-approval",
6343
+ name,
6344
+ line: "this machine is waiting to be approved in the Tailscale admin console"
6345
+ };
8063
6346
  case "Stopped":
8064
- throw new TailscaleError("Tailscale is disconnected; run `tailscale up`, then this again");
6347
+ return { state: "signed-out", name, line: "Tailscale is disconnected; run `tailscale up`, then this again" };
8065
6348
  default:
8066
- throw new TailscaleError(`Tailscale is not connected yet (${st.BackendState ?? "no state"}); try again shortly`);
6349
+ return {
6350
+ state: "not-running",
6351
+ name,
6352
+ line: `Tailscale is not connected yet (${st.BackendState ?? "no state"}); try again shortly`
6353
+ };
8067
6354
  }
8068
- const name = (st.Self?.DNSName ?? "").replace(/\.$/, "").toLowerCase();
8069
6355
  if (!name || st.CurrentTailnet?.MagicDNSEnabled === false) {
8070
- throw new TailscaleError("MagicDNS is off for this tailnet; turn it on under DNS in the Tailscale admin console");
6356
+ return {
6357
+ state: "magicdns-off",
6358
+ name,
6359
+ line: "MagicDNS is off for this tailnet; turn it on under DNS in the Tailscale admin console"
6360
+ };
8071
6361
  }
8072
6362
  if (!(st.CertDomains ?? []).some((d) => d.toLowerCase() === name)) {
8073
- throw new TailscaleError("HTTPS certificates are off for this tailnet; turn them on under DNS in the Tailscale admin console");
6363
+ return {
6364
+ state: "https-off",
6365
+ name,
6366
+ line: "HTTPS certificates are off for this tailnet; turn them on under DNS in the Tailscale admin console"
6367
+ };
8074
6368
  }
8075
- return name;
6369
+ return { state: "ready", name, line: `this machine is ${name} on your tailnet` };
6370
+ }
6371
+ async function readiness(ts) {
6372
+ let st;
6373
+ try {
6374
+ st = await ts(["status", "--json"]);
6375
+ } catch (e) {
6376
+ if (e instanceof TailscaleError)
6377
+ return { state: "not-installed", name: null, line: e.message };
6378
+ throw e;
6379
+ }
6380
+ if (!st.ok && notRunning(st.err + st.out))
6381
+ return { state: "not-running", name: null, line: NOT_RUNNING };
6382
+ return readinessOf(st.out || st.err);
8076
6383
  }
8077
6384
  function parseServeConfig(json) {
8078
6385
  const text = json.trim();
@@ -8107,10 +6414,10 @@ async function readConfig(ts) {
8107
6414
  return parseServeConfig(r.out);
8108
6415
  }
8109
6416
  async function serveTailnet(ts, daemonPort) {
8110
- const st = await ts(["status", "--json"]);
8111
- if (!st.ok && notRunning(st.err + st.out))
8112
- throw new TailscaleError(NOT_RUNNING);
8113
- const name = tailnetName(st.out || st.err);
6417
+ const r = await readiness(ts);
6418
+ if (r.state !== "ready" || r.name === null)
6419
+ throw new TailscaleError(r.line);
6420
+ const name = r.name;
8114
6421
  const config = await readConfig(ts);
8115
6422
  const want = entries(daemonPort).map((e) => ({ e, h: holder(config, name, e) }));
8116
6423
  const taken = want.flatMap(({ e, h }) => h.kind === "other" ? [`https ${e.port} (${h.what})`] : []);
@@ -8140,25 +6447,44 @@ async function unserveTailnet(ts, daemonPort) {
8140
6447
  }
8141
6448
  return removed;
8142
6449
  }
6450
+ async function turnTailnetOn(ts, daemonPort, file) {
6451
+ const host = await serveTailnet(ts, daemonPort);
6452
+ const view = { host, previews: portPreviews(host) };
6453
+ writeFileSync4(file, `${JSON.stringify(view, null, 2)}
6454
+ `);
6455
+ return view;
6456
+ }
6457
+ async function turnRemoteOff(ts, daemonPort, file, was) {
6458
+ rmSync2(file, { force: true });
6459
+ if (was && isTailnetName(was.host))
6460
+ await unserveTailnet(ts, daemonPort);
6461
+ }
8143
6462
 
8144
6463
  // src/remote.ts
8145
6464
  function saved() {
8146
- if (!existsSync8(remoteFile))
6465
+ if (!existsSync9(remoteFile))
8147
6466
  return null;
8148
6467
  return parseRemote(readFileSync7(remoteFile, "utf8"));
8149
6468
  }
8150
- var same = (a, b) => (a?.host ?? null) === (b?.host ?? null) && (a?.previews ?? null) === (b?.previews ?? null);
8151
6469
  var ports = `${PREVIEW_PORTS.from}-${PREVIEW_PORTS.to}`;
8152
- function describe2(r) {
8153
- const at = addressedByPort(r.previews) ? `${r.previews}, ports ${ports}` : r.previews;
8154
- return `remote access: https://${r.host}/, previews at ${at}`;
6470
+ var describe2 = (r) => `remote access: https://${r.host}/, ${previewsLine(r)}`;
6471
+ async function switchRemote(body) {
6472
+ const res = await daemonFetch("/remote", { body });
6473
+ if (!res)
6474
+ return "Toyon is not answering";
6475
+ if (!res.ok)
6476
+ return await res.text() || `the daemon refused (${res.status})`;
6477
+ return await res.json();
8155
6478
  }
8156
- function save(r) {
8157
- mkdirSync5(home, { recursive: true });
8158
- writeFileSync4(remoteFile, `${JSON.stringify(r, null, 2)}
8159
- `);
8160
- console.log(describe2(r));
6479
+ async function switched(body) {
6480
+ const r = await switchRemote(body);
6481
+ if (typeof r === "string") {
6482
+ console.error(`toyon: ${r}`);
6483
+ return false;
6484
+ }
6485
+ return r.remote;
8161
6486
  }
6487
+ var HOW_TO_OPEN = 'open it on your phone from the phone button in Toyon, or pick this machine under "add a machine"';
8162
6488
  async function remote(cmd) {
8163
6489
  const managed = await loadManaged();
8164
6490
  if (managed.policy.remote === "off")
@@ -8169,83 +6495,144 @@ async function remote(cmd) {
8169
6495
  }
8170
6496
  const h = await health();
8171
6497
  if (cmd.to === null && !cmd.tailscale) {
8172
- const r = saved();
8173
- console.log(r ? describe2(r) : "remote access is off; `toyon remote <name>` turns it on");
8174
- if (h && !same(h.remote, r))
8175
- console.log("the running daemon started before this; `toyon stop` then `toyon` applies it");
6498
+ const r = h ? h.remote ?? null : saved();
6499
+ console.log(r ? describe2(r) : "remote access is off; `toyon remote --tailscale` turns it on");
6500
+ const ts = h?.tailscale ?? await readiness(tailscaleCli());
6501
+ if (ts && !(r && ts.state === "ready"))
6502
+ console.log(ts.line);
8176
6503
  return 0;
8177
6504
  }
8178
- let r = null;
8179
6505
  if (cmd.tailscale) {
8180
- let name;
8181
- try {
8182
- name = await serveTailnet(tailscaleCli(), port);
8183
- } catch (e) {
8184
- if (!(e instanceof TailscaleError))
8185
- throw e;
8186
- console.error(`toyon: ${e.message}`);
8187
- return 1;
6506
+ if (h) {
6507
+ const now = await switched({ tailscale: true });
6508
+ if (now === false)
6509
+ return 1;
6510
+ if (now)
6511
+ console.log(describe2(now));
6512
+ } else {
6513
+ let r;
6514
+ try {
6515
+ mkdirSync5(home, { recursive: true });
6516
+ r = await turnTailnetOn(tailscaleCli(), port, remoteFile);
6517
+ } catch (e) {
6518
+ if (!(e instanceof TailscaleError))
6519
+ throw e;
6520
+ console.error(`toyon: ${e.message}`);
6521
+ return 1;
6522
+ }
6523
+ console.log(`${describe2(r)}; Toyon reads it when it starts`);
8188
6524
  }
8189
- r = { host: name, previews: portPreviews(name) };
8190
- save(r);
8191
- console.log(`tailscale serve sends https://${name}/ to 127.0.0.1:${port}, and each of ports ${ports} to the`);
8192
- console.log("same port on 127.0.0.1; only devices on your tailnet reach them");
8193
- } else if (cmd.to === "off") {
8194
- const was = saved();
8195
- rmSync3(remoteFile, { force: true });
8196
- console.log("remote access is off");
8197
- try {
8198
- const removed = await unserveTailnet(tailscaleCli(), port);
8199
- if (removed > 0)
8200
- console.log(`removed Toyon's ${removed} tailscale serve entries`);
8201
- } catch (e) {
8202
- if (!(e instanceof TailscaleError))
8203
- throw e;
8204
- if (was && isTailnetName(was.host))
6525
+ console.log(HOW_TO_OPEN);
6526
+ return 0;
6527
+ }
6528
+ if (cmd.to === "off") {
6529
+ if (h) {
6530
+ if (await switched({ off: true }) === false)
6531
+ return 1;
6532
+ } else {
6533
+ try {
6534
+ await turnRemoteOff(tailscaleCli(), port, remoteFile, saved());
6535
+ } catch (e) {
6536
+ if (!(e instanceof TailscaleError))
6537
+ throw e;
8205
6538
  console.error(`toyon: could not remove tailscale serve entries: ${e.message}`);
6539
+ }
8206
6540
  }
8207
- } else if (cmd.to !== null) {
8208
- r = { host: cmd.to, previews: cmd.ports ? portPreviews(cmd.to) : hostPreviews(cmd.to) };
8209
- save(r);
8210
- if (addressedByPort(r.previews)) {
8211
- console.log(`point a TLS front for ${r.host} at 127.0.0.1:${port} on 443, and each of ports ${ports} at`);
8212
- console.log("the same port on 127.0.0.1");
8213
- } else {
8214
- console.log(`point a TLS front for ${r.host} and *.${r.host} at 127.0.0.1:${port}. With Caddy:
6541
+ console.log("remote access is off");
6542
+ return 0;
6543
+ }
6544
+ const to = cmd.to;
6545
+ const view = acceptRemote({ host: to, previews: cmd.ports ? portPreviews(to) : hostPreviews(to) }, managed.policy);
6546
+ if (typeof view === "string") {
6547
+ console.error(`toyon: ${view}`);
6548
+ return 1;
6549
+ }
6550
+ if (h) {
6551
+ if (await switched(view) === false)
6552
+ return 1;
6553
+ } else {
6554
+ mkdirSync5(home, { recursive: true });
6555
+ writeFileSync5(remoteFile, `${JSON.stringify(view, null, 2)}
6556
+ `);
6557
+ }
6558
+ console.log(describe2(view));
6559
+ if (addressedByPort(view.previews)) {
6560
+ console.log(`point a TLS front for ${view.host} at 127.0.0.1:${port} on 443, and each of ports ${ports} at`);
6561
+ console.log("the same port on 127.0.0.1");
6562
+ } else {
6563
+ console.log(`point a TLS front for ${view.host} and *.${view.host} at 127.0.0.1:${port}. With Caddy:
8215
6564
  `);
8216
- console.log(` ${r.host}, *.${r.host} {
6565
+ console.log(` ${view.host}, *.${view.host} {
8217
6566
  reverse_proxy 127.0.0.1:${port}
8218
6567
  }
8219
6568
  `);
8220
- console.log("the wildcard certificate needs Caddy's DNS challenge, through your DNS provider's module");
6569
+ console.log("the wildcard certificate needs Caddy's DNS challenge, through your DNS provider's module");
6570
+ }
6571
+ console.log("Toyon refuses the name over plain http; a device that opens it asks to be let in");
6572
+ if (!h)
6573
+ console.log("Toyon reads this when it starts");
6574
+ return 0;
6575
+ }
6576
+
6577
+ // src/pair.ts
6578
+ var POLL_MS = 2000;
6579
+ async function waiting() {
6580
+ const r = await daemonFetch("/knocks");
6581
+ return r?.ok ? await r.json() : [];
6582
+ }
6583
+ async function pair() {
6584
+ const h = await health();
6585
+ if (!h) {
6586
+ console.error("toyon: Toyon is not running. Start it with `toyon`, then run `toyon pair`.");
6587
+ return 1;
6588
+ }
6589
+ let remote = h.remote ?? null;
6590
+ if (!remote) {
6591
+ const r = await switchRemote({ tailscale: true });
6592
+ if (typeof r === "string") {
6593
+ console.error(`toyon: ${r}`);
6594
+ return 1;
8221
6595
  }
8222
- console.log("Toyon refuses the name over plain http: the token in the link grants a shell on this machine");
6596
+ remote = r.remote;
8223
6597
  }
8224
- if (r) {
8225
- const add = launcherAddLink(`https://${r.host}`);
8226
- console.log(`add it to your list at toyon.cloud: ${add}`);
8227
- openUrl(add);
6598
+ if (!remote) {
6599
+ console.error("toyon: remote access did not come on; `toyon remote` says what Tailscale is missing");
6600
+ return 1;
8228
6601
  }
8229
- if (h && !same(h.remote, r)) {
8230
- console.log("the daemon reads this at start; `toyon stop` then `toyon` applies it");
8231
- if (r)
8232
- console.log("after that, `toyon pair` shows a code to scan with your phone");
8233
- } else if (h && r) {
8234
- const refused = await printPairCode();
8235
- if (refused)
8236
- console.error(`toyon: ${refused}`);
6602
+ console.log(machineLink(remote.host));
6603
+ console.log(HOW_TO_OPEN);
6604
+ if (isTailnetName(remote.host)) {
6605
+ const r = await daemonFetch("/phones");
6606
+ const phones = r?.ok ? await r.json() : null;
6607
+ console.log(tailnetLine(phones).text);
6608
+ }
6609
+ console.log("waiting for a device; ctrl-c to stop");
6610
+ const asked = new Set;
6611
+ for (;; ) {
6612
+ for (const k of await waiting()) {
6613
+ if (asked.has(k.id))
6614
+ continue;
6615
+ asked.add(k.id);
6616
+ const from = k.from === null ? "a device that opened the address" : `a Toyon page at ${new URL(k.from).host}`;
6617
+ const yes = await confirm(`${k.word} wants in (${from}). Let it in?`);
6618
+ const r = await daemonFetch(`/knock/${k.id}/answer`, { body: { letIn: yes } });
6619
+ if (!r?.ok)
6620
+ console.log(`${k.word} stopped waiting`);
6621
+ else
6622
+ console.log(yes ? `${k.word} is in` : `${k.word} turned away`);
6623
+ }
6624
+ await Bun.sleep(POLL_MS);
8237
6625
  }
8238
- return 0;
8239
6626
  }
8240
6627
 
8241
6628
  // src/stop.ts
8242
- import { rmSync as rmSync4 } from "fs";
6629
+ import { rmSync as rmSync3 } from "fs";
8243
6630
  var WAIT_MS = 1e4;
8244
6631
  async function stop() {
8245
6632
  const h = await health();
8246
6633
  let pid = h?.pid ?? readPid();
8247
6634
  if (pid !== null && !alive(pid)) {
8248
- rmSync4(pidFile, { force: true });
6635
+ rmSync3(pidFile, { force: true });
8249
6636
  pid = null;
8250
6637
  }
8251
6638
  if (pid === null) {
@@ -8264,7 +6651,7 @@ async function stop() {
8264
6651
  if (alive(pid)) {
8265
6652
  process.kill(pid, "SIGKILL");
8266
6653
  console.error(`daemon (pid ${pid}) did not exit in ${WAIT_MS / 1000}s; killed. Dev servers it ran may still be up.`);
8267
- rmSync4(pidFile, { force: true });
6654
+ rmSync3(pidFile, { force: true });
8268
6655
  return 1;
8269
6656
  }
8270
6657
  console.log(`stopped Toyon (pid ${pid})`);
@@ -8291,12 +6678,11 @@ async function restart() {
8291
6678
  }
8292
6679
 
8293
6680
  // src/uninstall.ts
8294
- import { existsSync as existsSync9, readFileSync as readFileSync8, rmSync as rmSync5 } from "fs";
8295
- import { homedir as homedir3 } from "os";
6681
+ import { existsSync as existsSync10, readFileSync as readFileSync8, rmSync as rmSync4 } from "fs";
8296
6682
  import { join as join6 } from "path";
8297
6683
  function readState() {
8298
6684
  const file = join6(home, DAEMON_FILES.state);
8299
- if (!existsSync9(file))
6685
+ if (!existsSync10(file))
8300
6686
  return {};
8301
6687
  try {
8302
6688
  return JSON.parse(readFileSync8(file, "utf8"));
@@ -8313,18 +6699,11 @@ async function git(cwd, ...args) {
8313
6699
  return false;
8314
6700
  }
8315
6701
  }
8316
- function confirm(question) {
8317
- process.stdout.write(`${question} [y/N] `);
8318
- return new Promise((resolve) => {
8319
- process.stdin.once("data", (chunk) => resolve(String(chunk).trim().toLowerCase().startsWith("y")));
8320
- process.stdin.once("end", () => resolve(false));
8321
- });
8322
- }
8323
6702
  async function uninstall(opts) {
8324
6703
  const state = readState();
8325
6704
  const repos = state.repos ?? [];
8326
6705
  const worktrees = (state.worktrees ?? []).filter((w) => w.kind !== "main");
8327
- const app = join6(homedir3(), "Applications", "Toyon.app");
6706
+ const helper = join6(home, DAEMON_FILES.helper);
8328
6707
  const branches = worktrees.map((w) => w.branch).filter((b) => !!b && b.startsWith("toyon/"));
8329
6708
  console.log("toyon uninstall removes:");
8330
6709
  console.log(` ${home} (state, transcripts, agent adapters, ${worktrees.length} worktree director${worktrees.length === 1 ? "y" : "ies"})`);
@@ -8332,8 +6711,8 @@ async function uninstall(opts) {
8332
6711
  const repo = repos.find((r) => r.id === w.repoId);
8333
6712
  console.log(` ${w.path} (worktree of ${repo?.path ?? "?"})`);
8334
6713
  }
8335
- if (existsSync9(app))
8336
- console.log(` ${app}`);
6714
+ if (existsSync10(helper))
6715
+ console.log(` ${helper} (the hidden helper that starts Toyon from the Dock app)`);
8337
6716
  console.log("and stops the daemon if it is running.");
8338
6717
  console.log("it keeps: your repos, every branch Toyon made" + (branches.length ? ` (${branches.length} under toyon/)` : "") + ", and Toyon's settings in each repo (.toyon/ or toyon.json).");
8339
6718
  if (!opts.yes && !await confirm("continue?")) {
@@ -8344,18 +6723,21 @@ async function uninstall(opts) {
8344
6723
  await stop();
8345
6724
  for (const w of worktrees) {
8346
6725
  const repo = repos.find((r) => r.id === w.repoId);
8347
- if (repo && existsSync9(repo.path)) {
6726
+ if (repo && existsSync10(repo.path)) {
8348
6727
  const ok = await git(repo.path, "worktree", "remove", "--force", w.path);
8349
- if (!ok && existsSync9(w.path))
8350
- rmSync5(w.path, { recursive: true, force: true });
6728
+ if (!ok && existsSync10(w.path))
6729
+ rmSync4(w.path, { recursive: true, force: true });
8351
6730
  await git(repo.path, "worktree", "prune");
8352
- } else if (existsSync9(w.path)) {
8353
- rmSync5(w.path, { recursive: true, force: true });
6731
+ } else if (existsSync10(w.path)) {
6732
+ rmSync4(w.path, { recursive: true, force: true });
8354
6733
  }
8355
6734
  }
8356
- rmSync5(home, { recursive: true, force: true });
8357
- if (existsSync9(app))
8358
- rmSync5(app, { recursive: true, force: true });
6735
+ if (existsSync10(helper)) {
6736
+ try {
6737
+ await Bun.spawn([LSREGISTER, "-u", helper], { stdout: "ignore", stderr: "ignore" }).exited;
6738
+ } catch {}
6739
+ }
6740
+ rmSync4(home, { recursive: true, force: true });
8359
6741
  console.log("removed. `npm uninstall -g toyon` removes the command itself.");
8360
6742
  return 0;
8361
6743
  }
@@ -8445,13 +6827,10 @@ async function restartOnto(version) {
8445
6827
  }
8446
6828
 
8447
6829
  // src/cli.ts
8448
- function behind(daemonVersion) {
8449
- return daemonVersion === undefined || newer(package_default.version, daemonVersion);
8450
- }
8451
6830
  async function open2(cmd) {
8452
6831
  const explicit = cmd.path !== null;
8453
6832
  const target = resolve(cmd.path ?? process.cwd());
8454
- const register = explicit || existsSync10(join7(target, ".git"));
6833
+ const register = explicit || existsSync11(join7(target, ".git"));
8455
6834
  if (process.platform === "linux") {
8456
6835
  const missing = missingSandboxTools();
8457
6836
  const blocked = missing.length === 0 ? bwrapStartError() : null;
@@ -8460,28 +6839,12 @@ async function open2(cmd) {
8460
6839
  else if (blocked)
8461
6840
  console.log(bwrapBlockedAdvice(blocked, userNamespacesRestricted()));
8462
6841
  }
8463
- const running = await health();
8464
- if (!running) {
8465
- console.log("starting Toyon daemon\u2026");
8466
- if (!await startDaemon()) {
8467
- console.error(`daemon failed to start; \`toyon logs\` shows why (${logFile})`);
8468
- return 1;
8469
- }
8470
- } else if (behind(running.version)) {
8471
- console.log(`toyon: the running daemon is ${running.version ?? "older"}; \`${restartCmd}\` starts ${package_default.version}`);
8472
- }
8473
- const token = readToken();
8474
- if (!token) {
8475
- console.error("daemon token missing; `toyon doctor` says where it looked");
6842
+ const daemon = await ensureDaemon();
6843
+ if (!daemon)
8476
6844
  return 1;
8477
- }
8478
- const file = explicit && existsSync10(target) && statSync4(target).isFile();
6845
+ const file = explicit && existsSync11(target) && statSync4(target).isFile();
8479
6846
  if (file) {
8480
- const res = await fetch(`${base}/open`, {
8481
- method: "POST",
8482
- headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
8483
- body: JSON.stringify({ path: target })
8484
- });
6847
+ const res = await post("/open", daemon.token, { path: target });
8485
6848
  if (!res.ok) {
8486
6849
  console.error(`could not open ${target}: ${await res.text()}`);
8487
6850
  return 1;
@@ -8489,39 +6852,34 @@ async function open2(cmd) {
8489
6852
  const opened = await res.json();
8490
6853
  console.log(opened.kind === "file" ? `toyon: opening ${target} in its project` : `toyon: opening ${target}`);
8491
6854
  } else if (register) {
8492
- const res = await fetch(`${base}/register`, {
8493
- method: "POST",
8494
- headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
8495
- body: JSON.stringify({ path: target })
8496
- });
6855
+ const res = await post("/register", daemon.token, { path: target });
8497
6856
  if (!res.ok) {
8498
6857
  console.error(`could not open ${target}: ${await res.text()}`);
8499
- if (explicit || !cmd.app && !cmd.installApp)
6858
+ if (explicit || !cmd.app)
8500
6859
  return 1;
8501
6860
  }
8502
6861
  }
8503
- const branded = (await health())?.branded === true;
8504
- const url = shellUrl(token, branded);
8505
- const appUrl = `http://toyon.localhost:${port}/#token=${token}`;
8506
- if (cmd.installApp) {
8507
- installApp(appUrl);
8508
- } else if (cmd.app) {
8509
- console.log(`toyon: app window (${appUrl.split("#")[0]})`);
8510
- if (!openAppWindow(appUrl)) {
8511
- console.log("no Chromium browser found; opening in default browser");
8512
- openUrl(url);
8513
- }
8514
- } else {
8515
- console.log(`toyon: ${url}`);
8516
- openUrl(url);
6862
+ if (cmd.app)
6863
+ showAppWindow(daemon);
6864
+ else {
6865
+ console.log(`toyon: ${daemon.url}`);
6866
+ openUrl(daemon.url);
8517
6867
  }
8518
- const remote = (await health())?.remote;
8519
- if (remote)
8520
- console.log(`toyon: remote at https://${remote.host}/#token=${token}`);
6868
+ return 0;
6869
+ }
6870
+ async function start() {
6871
+ const daemon = await ensureDaemon();
6872
+ if (!daemon)
6873
+ return 1;
6874
+ console.log(`toyon: ${daemon.url}`);
8521
6875
  return 0;
8522
6876
  }
8523
6877
  async function run(cmd) {
8524
6878
  switch (cmd.kind) {
6879
+ case "start":
6880
+ return start();
6881
+ case "helper":
6882
+ return helper(cmd.args);
8525
6883
  case "help":
8526
6884
  process.stdout.write(HELP);
8527
6885
  return 0;