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 +1 -1
- package/scripts/viveworker-bridge.mjs +59 -10
- package/scripts/viveworker.mjs +2 -2
- package/web/app.js +12 -9
- package/web/sw.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "viveworker",
|
|
3
|
-
"version": "0.1.
|
|
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) && !
|
|
4643
|
+
return isPairingAvailable(config) && !pairingCodeConsumed(config, state);
|
|
4627
4644
|
}
|
|
4628
4645
|
|
|
4629
|
-
function
|
|
4630
|
-
const
|
|
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
|
-
|
|
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 (!
|
|
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 (
|
|
4655
|
-
return { ok:
|
|
4683
|
+
if (matchesToken) {
|
|
4684
|
+
return { ok: true, credential: `token:${token}` };
|
|
4656
4685
|
}
|
|
4657
|
-
|
|
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 (!
|
|
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
|
-
|
|
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);
|
package/scripts/viveworker.mjs
CHANGED
|
@@ -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({
|
|
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
|
-
|
|
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