@synkro-sh/cli 1.4.60 → 1.4.61
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/dist/bootstrap.js +67 -37
- package/dist/bootstrap.js.map +1 -1
- package/package.json +2 -1
package/dist/bootstrap.js
CHANGED
|
@@ -911,63 +911,93 @@ function decodeJwtExp(jwt: string): number {
|
|
|
911
911
|
}
|
|
912
912
|
|
|
913
913
|
export async function refreshJwt(jwt: string): Promise<string> {
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
if (!rt) return jwt;
|
|
914
|
+
const creds = JSON.parse(readFileSync(CREDS_PATH, 'utf-8'));
|
|
915
|
+
const rt = creds.refresh_token;
|
|
916
|
+
if (!rt) return jwt;
|
|
918
917
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
const newRt = data.refresh_token || rt;
|
|
930
|
-
const existing = (() => {
|
|
931
|
-
try { return JSON.parse(readFileSync(CREDS_PATH, 'utf-8')); } catch { return {}; }
|
|
932
|
-
})();
|
|
933
|
-
const updated = { ...existing, access_token: newAt, refresh_token: newRt };
|
|
934
|
-
const tmp = CREDS_PATH + '.synkro.tmp';
|
|
935
|
-
writeFileSync(tmp, JSON.stringify(updated, null, 2));
|
|
936
|
-
renameSync(tmp, CREDS_PATH);
|
|
937
|
-
return newAt;
|
|
938
|
-
} catch {
|
|
918
|
+
const resp = await fetch(GATEWAY_URL + '/api/auth/refresh', {
|
|
919
|
+
method: 'POST',
|
|
920
|
+
headers: { 'Content-Type': 'application/json' },
|
|
921
|
+
body: JSON.stringify({ refresh_token: rt }),
|
|
922
|
+
signal: AbortSignal.timeout(4000),
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
if (!resp.ok) {
|
|
926
|
+
log('refresh failed: HTTP ' + resp.status);
|
|
939
927
|
return jwt;
|
|
940
928
|
}
|
|
929
|
+
|
|
930
|
+
const data = await resp.json() as any;
|
|
931
|
+
const newAt = data.access_token;
|
|
932
|
+
if (!newAt) return jwt;
|
|
933
|
+
|
|
934
|
+
const newRt = data.refresh_token || rt;
|
|
935
|
+
const existing = (() => {
|
|
936
|
+
try { return JSON.parse(readFileSync(CREDS_PATH, 'utf-8')); } catch { return {}; }
|
|
937
|
+
})();
|
|
938
|
+
const updated = { ...existing, access_token: newAt, refresh_token: newRt };
|
|
939
|
+
const tmp = CREDS_PATH + '.synkro.tmp';
|
|
940
|
+
writeFileSync(tmp, JSON.stringify(updated, null, 2));
|
|
941
|
+
renameSync(tmp, CREDS_PATH);
|
|
942
|
+
return newAt;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
function jwtIsExpired(jwt: string): boolean {
|
|
946
|
+
const exp = decodeJwtExp(jwt);
|
|
947
|
+
return exp - Math.floor(Date.now() / 1000) < 60;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
function lockIsStale(lockfile: string, maxAgeMs = 8000): boolean {
|
|
951
|
+
try {
|
|
952
|
+
const stat = require('node:fs').statSync(lockfile);
|
|
953
|
+
return Date.now() - stat.mtimeMs > maxAgeMs;
|
|
954
|
+
} catch {
|
|
955
|
+
return false;
|
|
956
|
+
}
|
|
941
957
|
}
|
|
942
958
|
|
|
943
959
|
export async function ensureFreshJwt(jwt: string): Promise<string> {
|
|
944
960
|
if (!jwt) return jwt;
|
|
945
|
-
|
|
946
|
-
const now = Math.floor(Date.now() / 1000);
|
|
947
|
-
if (exp - now >= 60) return jwt;
|
|
961
|
+
if (!jwtIsExpired(jwt)) return jwt;
|
|
948
962
|
|
|
949
963
|
const lockfile = CREDS_PATH + '.lock';
|
|
964
|
+
|
|
965
|
+
// Clean stale lock left by a killed hook
|
|
966
|
+
if (existsSync(lockfile) && lockIsStale(lockfile)) {
|
|
967
|
+
try { unlinkSync(lockfile); } catch {}
|
|
968
|
+
}
|
|
969
|
+
|
|
950
970
|
let fd = -1;
|
|
951
971
|
try {
|
|
952
972
|
fd = openSync(lockfile, FS_CONSTANTS.O_WRONLY | FS_CONSTANTS.O_CREAT | FS_CONSTANTS.O_EXCL, 0o644);
|
|
953
973
|
} catch {
|
|
954
|
-
// Another process
|
|
955
|
-
for (let i = 0; i <
|
|
956
|
-
await new Promise(r => setTimeout(r,
|
|
974
|
+
// Another process is refreshing \u2014 wait for it
|
|
975
|
+
for (let i = 0; i < 8; i++) {
|
|
976
|
+
await new Promise(r => setTimeout(r, 500));
|
|
957
977
|
if (!existsSync(lockfile)) break;
|
|
958
978
|
}
|
|
979
|
+
// Stale lock after full wait \u2014 force-remove
|
|
980
|
+
if (existsSync(lockfile) && lockIsStale(lockfile)) {
|
|
981
|
+
try { unlinkSync(lockfile); } catch {}
|
|
982
|
+
}
|
|
983
|
+
// Re-read \u2014 the winner should have written a fresh JWT
|
|
959
984
|
const fresh = loadJwt();
|
|
960
|
-
|
|
985
|
+
if (fresh && !jwtIsExpired(fresh)) return fresh;
|
|
986
|
+
// Winner's refresh failed \u2014 try to acquire lock ourselves
|
|
987
|
+
try {
|
|
988
|
+
fd = openSync(lockfile, FS_CONSTANTS.O_WRONLY | FS_CONSTANTS.O_CREAT | FS_CONSTANTS.O_EXCL, 0o644);
|
|
989
|
+
} catch {
|
|
990
|
+
return fresh || jwt;
|
|
991
|
+
}
|
|
961
992
|
}
|
|
962
993
|
|
|
963
994
|
try {
|
|
964
|
-
// Re-check \u2014 another hook may have
|
|
995
|
+
// Re-check \u2014 another hook may have written fresh creds while we waited for lock
|
|
965
996
|
const freshJwt = loadJwt();
|
|
966
|
-
if (freshJwt)
|
|
967
|
-
const freshExp = decodeJwtExp(freshJwt);
|
|
968
|
-
if (freshExp - Math.floor(Date.now() / 1000) >= 60) return freshJwt;
|
|
969
|
-
}
|
|
997
|
+
if (freshJwt && !jwtIsExpired(freshJwt)) return freshJwt;
|
|
970
998
|
return await refreshJwt(jwt);
|
|
999
|
+
} catch {
|
|
1000
|
+
return jwt;
|
|
971
1001
|
} finally {
|
|
972
1002
|
try { closeSync(fd); } catch {}
|
|
973
1003
|
try { unlinkSync(lockfile); } catch {}
|
|
@@ -5137,7 +5167,7 @@ function writeConfigEnv(opts) {
|
|
|
5137
5167
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
5138
5168
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
5139
5169
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
5140
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.4.
|
|
5170
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.4.61")}`
|
|
5141
5171
|
];
|
|
5142
5172
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
5143
5173
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|