viveworker 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viveworker",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Local iPhone companion for Codex Desktop approvals, plan checks, questions, and notifications on your LAN.",
5
5
  "author": "Yuta Hoshino <hoshino.lireneo@gmail.com>",
6
6
  "license": "MIT",
@@ -4409,6 +4409,7 @@ function readSession(req, config, state) {
4409
4409
  pairedAtMs: Number(payload.pairedAtMs) || 0,
4410
4410
  expiresAtMs: Number(payload.expiresAtMs) || 0,
4411
4411
  deviceId,
4412
+ temporaryPairing: payload?.temporaryPairing === true,
4412
4413
  };
4413
4414
  }
4414
4415
 
@@ -4466,6 +4467,22 @@ function setSessionCookie(res, config) {
4466
4467
  }));
4467
4468
  }
4468
4469
 
4470
+ function setTemporarySessionCookie(res, config) {
4471
+ const secure = config.nativeApprovalPublicBaseUrl.startsWith("https://");
4472
+ const now = Date.now();
4473
+ const token = signSessionPayload({
4474
+ sessionId: crypto.randomUUID(),
4475
+ pairedAtMs: now,
4476
+ expiresAtMs: now + config.sessionTtlMs,
4477
+ temporaryPairing: true,
4478
+ }, config.sessionSecret);
4479
+ res.setHeader("Set-Cookie", buildSetCookieHeader({
4480
+ value: token,
4481
+ maxAgeSecs: Math.max(1, Math.floor(config.sessionTtlMs / 1000)),
4482
+ secure,
4483
+ }));
4484
+ }
4485
+
4469
4486
  function clearSessionCookie(res, config) {
4470
4487
  const secure = config.nativeApprovalPublicBaseUrl.startsWith("https://");
4471
4488
  res.setHeader("Set-Cookie", buildSetCookieHeader({ value: "", maxAgeSecs: 0, secure }));
@@ -4623,15 +4640,27 @@ function pairingCredentialConsumed(config, state) {
4623
4640
  }
4624
4641
 
4625
4642
  function isPairingAvailableForState(config, state) {
4626
- return isPairingAvailable(config) && !pairingCredentialConsumed(config, state);
4643
+ return isPairingAvailable(config) && !pairingCodeConsumed(config, state);
4627
4644
  }
4628
4645
 
4629
- function markPairingConsumed(state, config, now = Date.now()) {
4630
- const current = currentPairingCredential(config);
4646
+ function pairingCodeConsumed(config, state) {
4647
+ const code = cleanText(config?.pairingCode ?? "").toUpperCase();
4648
+ if (!code) {
4649
+ return false;
4650
+ }
4651
+ const consumedAtMs = Number(state?.pairingConsumedAt) || 0;
4652
+ const consumedCredential = cleanText(state?.pairingConsumedCredential ?? "");
4653
+ return consumedAtMs > 0 && consumedCredential === `code:${code}`;
4654
+ }
4655
+
4656
+ function markPairingConsumed(state, credential, now = Date.now()) {
4657
+ const current = cleanText(credential || "");
4631
4658
  if (!current) {
4632
4659
  return false;
4633
4660
  }
4634
- if (pairingCredentialConsumed(config, state)) {
4661
+ const consumedAtMs = Number(state?.pairingConsumedAt) || 0;
4662
+ const consumedCredential = cleanText(state?.pairingConsumedCredential ?? "");
4663
+ if (consumedAtMs > 0 && consumedCredential === current) {
4635
4664
  return false;
4636
4665
  }
4637
4666
  state.pairingConsumedAt = now;
@@ -4643,7 +4672,7 @@ function validatePairingPayload(payload, config, state) {
4643
4672
  if (!config.authRequired) {
4644
4673
  return { ok: true };
4645
4674
  }
4646
- if (!isPairingAvailableForState(config, state)) {
4675
+ if (!isPairingAvailable(config)) {
4647
4676
  return { ok: false, error: "pairing-unavailable" };
4648
4677
  }
4649
4678
 
@@ -4651,10 +4680,16 @@ function validatePairingPayload(payload, config, state) {
4651
4680
  const token = cleanText(payload?.token ?? "");
4652
4681
  const matchesCode = code && cleanText(config.pairingCode).toUpperCase() === code;
4653
4682
  const matchesToken = token && cleanText(config.pairingToken) === token;
4654
- if (!matchesCode && !matchesToken) {
4655
- return { ok: false, error: "invalid-pairing-credentials" };
4683
+ if (matchesToken) {
4684
+ return { ok: true, credential: `token:${token}` };
4656
4685
  }
4657
- return { ok: true };
4686
+ if (matchesCode) {
4687
+ if (pairingCodeConsumed(config, state)) {
4688
+ return { ok: false, error: "pairing-unavailable" };
4689
+ }
4690
+ return { ok: true, credential: `code:${code}` };
4691
+ }
4692
+ return { ok: false, error: "invalid-pairing-credentials" };
4658
4693
  }
4659
4694
 
4660
4695
  function readRemoteAddress(req) {
@@ -5707,7 +5742,7 @@ function resolveManifestPairingToken({ config, state, requestedToken }) {
5707
5742
  if (!token) {
5708
5743
  return "";
5709
5744
  }
5710
- if (!isPairingAvailableForState(config, state)) {
5745
+ if (!isPairingAvailable(config)) {
5711
5746
  return "";
5712
5747
  }
5713
5748
  return cleanText(config.pairingToken) === token ? token : "";
@@ -5860,6 +5895,7 @@ function createNativeApprovalServer({ config, runtime, state }) {
5860
5895
  httpsEnabled: config.nativeApprovalPublicBaseUrl.startsWith("https://"),
5861
5896
  appVersion: appPackageVersion,
5862
5897
  deviceId: session.deviceId || null,
5898
+ temporaryPairing: session.temporaryPairing === true,
5863
5899
  ...buildSessionLocalePayload(config, state, session.deviceId),
5864
5900
  });
5865
5901
  }
@@ -5884,6 +5920,17 @@ function createNativeApprovalServer({ config, runtime, state }) {
5884
5920
  return writeJson(res, 400, { error: validation.error });
5885
5921
  }
5886
5922
 
5923
+ if (payload?.temporary === true && cleanText(payload?.token || "")) {
5924
+ clearPairingFailures(runtime, remoteAddress);
5925
+ setTemporarySessionCookie(res, config);
5926
+ return writeJson(res, 200, {
5927
+ ok: true,
5928
+ authenticated: true,
5929
+ pairingAvailable: isPairingAvailableForState(config, state),
5930
+ temporaryPairing: true,
5931
+ });
5932
+ }
5933
+
5887
5934
  const pairedDeviceId = readDeviceId(req, config) || crypto.randomUUID();
5888
5935
  if ("detectedLocale" in payload) {
5889
5936
  upsertDetectedDeviceLocale(state, pairedDeviceId, payload.detectedLocale);
@@ -5898,7 +5945,9 @@ function createNativeApprovalServer({ config, runtime, state }) {
5898
5945
  lastLocale: normalizeSupportedLocale(payload?.detectedLocale),
5899
5946
  }
5900
5947
  );
5901
- markPairingConsumed(state, config);
5948
+ if (String(validation.credential || "").startsWith("code:")) {
5949
+ markPairingConsumed(state, validation.credential);
5950
+ }
5902
5951
  clearPairingFailures(runtime, remoteAddress);
5903
5952
  await saveState(config.stateFile, state);
5904
5953
  setPairingCookies(res, config, pairedDeviceId);
@@ -265,7 +265,7 @@ async function runSetup(cliOptions) {
265
265
  if (allowInsecureHttpLan) {
266
266
  console.log(t(locale, "cli.setup.warning.insecureHttpLan"));
267
267
  }
268
- if (canShowCaDownload && !cliOptions.installMkcert) {
268
+ if (canShowCaDownload && !cliOptions.installMkcert && !cliOptions.pair) {
269
269
  console.log(t(locale, "cli.setup.caDownloadLocal", { url: caDownloadLocalUrl }));
270
270
  console.log(t(locale, "cli.setup.caDownloadIp", { url: caDownloadIpUrl }));
271
271
  }
@@ -280,7 +280,7 @@ async function runSetup(cliOptions) {
280
280
  console.log("");
281
281
  console.log(t(locale, "cli.setup.qrPairing"));
282
282
  await printQrCode(`${publicBaseUrl}${pairPath}`);
283
- if (canShowCaDownload && !cliOptions.installMkcert) {
283
+ if (canShowCaDownload && !cliOptions.installMkcert && !cliOptions.pair) {
284
284
  console.log("");
285
285
  console.log(t(locale, "cli.setup.qrCaDownload"));
286
286
  await printQrCode(caDownloadIpUrl);
package/web/app.js CHANGED
@@ -89,7 +89,10 @@ async function boot() {
89
89
 
90
90
  if (!state.session?.authenticated && initialPairToken && shouldAutoPairFromBootstrapToken()) {
91
91
  try {
92
- await pair({ token: initialPairToken });
92
+ await pair({
93
+ token: initialPairToken,
94
+ temporary: shouldUseTemporaryBootstrapPairing(),
95
+ });
93
96
  } catch (error) {
94
97
  state.pairError = error.message || String(error);
95
98
  }
@@ -532,7 +535,9 @@ function renderPair() {
532
535
 
533
536
  async function pair(payload) {
534
537
  const result = await apiPost("/api/session/pair", payload);
535
- syncPairingTokenState("");
538
+ if (result?.temporaryPairing !== true) {
539
+ syncPairingTokenState("");
540
+ }
536
541
  return result;
537
542
  }
538
543
 
@@ -4069,7 +4074,7 @@ function syncPairingTokenState(pairToken) {
4069
4074
  }
4070
4075
 
4071
4076
  function desiredBootstrapPairingToken() {
4072
- if (state.session?.authenticated) {
4077
+ if (state.session?.authenticated && !state.session?.temporaryPairing) {
4073
4078
  return "";
4074
4079
  }
4075
4080
  return initialPairToken;
@@ -4079,15 +4084,13 @@ function shouldAutoPairFromBootstrapToken() {
4079
4084
  if (!initialPairToken) {
4080
4085
  return false;
4081
4086
  }
4082
- if (isStandaloneMode()) {
4083
- return true;
4084
- }
4085
- if (isProbablySafari()) {
4086
- return false;
4087
- }
4088
4087
  return true;
4089
4088
  }
4090
4089
 
4090
+ function shouldUseTemporaryBootstrapPairing() {
4091
+ return Boolean(initialPairToken) && !isStandaloneMode() && isProbablySafari();
4092
+ }
4093
+
4091
4094
  function urlBase64ToUint8Array(base64String) {
4092
4095
  const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
4093
4096
  const normalized = `${base64String}${padding}`.replace(/-/gu, "+").replace(/_/gu, "/");
package/web/sw.js CHANGED
@@ -1,4 +1,4 @@
1
- const CACHE_NAME = "viveworker-v4";
1
+ const CACHE_NAME = "viveworker-v5";
2
2
  const APP_ASSETS = ["/app.css", "/app.js", "/i18n.js"];
3
3
  const APP_ROUTES = new Set(["/", "/app", "/app/"]);
4
4
  const CACHED_PATHS = new Set(APP_ASSETS);