bitfab-cli 0.2.33 → 0.2.34
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/index.js +117 -66
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6971,6 +6971,39 @@ async function pollLoginEvents(opts) {
|
|
|
6971
6971
|
}
|
|
6972
6972
|
throw new Error("Login polling aborted");
|
|
6973
6973
|
}
|
|
6974
|
+
async function fetchStreamTip(client) {
|
|
6975
|
+
const PAGE_LIMIT = 500;
|
|
6976
|
+
const MAX_PAGES = 10;
|
|
6977
|
+
let tip;
|
|
6978
|
+
try {
|
|
6979
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
6980
|
+
const url2 = new URL(`${client.serviceUrl}/api/studio/events`);
|
|
6981
|
+
url2.searchParams.set("session", client.sessionId);
|
|
6982
|
+
url2.searchParams.set("limit", String(PAGE_LIMIT));
|
|
6983
|
+
if (tip) {
|
|
6984
|
+
url2.searchParams.set("since", tip);
|
|
6985
|
+
}
|
|
6986
|
+
const res = await fetch(url2.toString(), {
|
|
6987
|
+
headers: { Authorization: `Bearer ${client.apiKey}` }
|
|
6988
|
+
});
|
|
6989
|
+
if (!res.ok) {
|
|
6990
|
+
return tip;
|
|
6991
|
+
}
|
|
6992
|
+
const body = await res.json();
|
|
6993
|
+
const events = body.events ?? [];
|
|
6994
|
+
const last = events.at(-1)?.id;
|
|
6995
|
+
if (!last) {
|
|
6996
|
+
return tip;
|
|
6997
|
+
}
|
|
6998
|
+
tip = last;
|
|
6999
|
+
if (events.length < PAGE_LIMIT) {
|
|
7000
|
+
return tip;
|
|
7001
|
+
}
|
|
7002
|
+
}
|
|
7003
|
+
} catch {
|
|
7004
|
+
}
|
|
7005
|
+
return tip;
|
|
7006
|
+
}
|
|
6974
7007
|
async function navigateStudio(client, path15) {
|
|
6975
7008
|
if (!isValidStudioRoute(path15)) {
|
|
6976
7009
|
throw new Error(`Studio route not allowed: ${path15}`);
|
|
@@ -6982,7 +7015,7 @@ async function navigateStudio(client, path15) {
|
|
|
6982
7015
|
}
|
|
6983
7016
|
var STUDIO_NAVIGATE_ACK_TIMEOUT_MS = 12e3;
|
|
6984
7017
|
var PING_PONG_TIMEOUT_MS = 5e3;
|
|
6985
|
-
function awaitNavigateAck(client, path15, ackTimeoutMs) {
|
|
7018
|
+
function awaitNavigateAck(client, path15, ackTimeoutMs, startCursor) {
|
|
6986
7019
|
const abortController = new AbortController();
|
|
6987
7020
|
let timer = null;
|
|
6988
7021
|
const result = new Promise((resolve) => {
|
|
@@ -6995,6 +7028,12 @@ function awaitNavigateAck(client, path15, ackTimeoutMs) {
|
|
|
6995
7028
|
serviceUrl: client.serviceUrl,
|
|
6996
7029
|
apiKey: client.apiKey,
|
|
6997
7030
|
sessionId: client.sessionId,
|
|
7031
|
+
// Only a FRESH ack counts: polling from the stream start would match a
|
|
7032
|
+
// historical `studio:navigated` for the same path (sessions revisit the
|
|
7033
|
+
// same routes constantly), false-acking a navigation a dead or
|
|
7034
|
+
// disconnected window never processed — masking exactly the
|
|
7035
|
+
// unreachable-window case that must gate.
|
|
7036
|
+
startCursor,
|
|
6998
7037
|
abortSignal: abortController.signal,
|
|
6999
7038
|
onEvent: (event) => {
|
|
7000
7039
|
if (event.type === "studio:navigated" && event.data.path === pathOnly) {
|
|
@@ -7017,11 +7056,11 @@ function awaitNavigateAck(client, path15, ackTimeoutMs) {
|
|
|
7017
7056
|
abortController.abort();
|
|
7018
7057
|
return;
|
|
7019
7058
|
}
|
|
7020
|
-
if (event.type === "studio:
|
|
7059
|
+
if (event.type === "studio:window-closed") {
|
|
7021
7060
|
if (timer) {
|
|
7022
7061
|
clearTimeout(timer);
|
|
7023
7062
|
}
|
|
7024
|
-
resolve({ acked: false, reason: "
|
|
7063
|
+
resolve({ acked: false, reason: "window-closed" });
|
|
7025
7064
|
abortController.abort();
|
|
7026
7065
|
}
|
|
7027
7066
|
},
|
|
@@ -7075,12 +7114,13 @@ async function navigateStudioAndAwaitAck(client, path15, opts = {}) {
|
|
|
7075
7114
|
if (!isValidStudioRoute(path15)) {
|
|
7076
7115
|
return { acked: false, reason: "invalid-route" };
|
|
7077
7116
|
}
|
|
7117
|
+
const startCursor = await fetchStreamTip(client);
|
|
7078
7118
|
try {
|
|
7079
7119
|
await navigateStudio(client, path15);
|
|
7080
7120
|
} catch {
|
|
7081
7121
|
return { acked: false, reason: "push-failed" };
|
|
7082
7122
|
}
|
|
7083
|
-
const result = await awaitNavigateAck(client, path15, ackTimeoutMs);
|
|
7123
|
+
const result = await awaitNavigateAck(client, path15, ackTimeoutMs, startCursor);
|
|
7084
7124
|
if (result.acked || result.reason !== "timeout") {
|
|
7085
7125
|
return result;
|
|
7086
7126
|
}
|
|
@@ -7374,7 +7414,8 @@ function readActiveStudioSessionRaw() {
|
|
|
7374
7414
|
sessionId: parsed.sessionId,
|
|
7375
7415
|
serviceUrl: parsed.serviceUrl,
|
|
7376
7416
|
pid: parsed.pid,
|
|
7377
|
-
startedAt: parsed.startedAt
|
|
7417
|
+
startedAt: parsed.startedAt,
|
|
7418
|
+
...typeof parsed.windowPid === "number" ? { windowPid: parsed.windowPid } : {}
|
|
7378
7419
|
};
|
|
7379
7420
|
}
|
|
7380
7421
|
return null;
|
|
@@ -7601,9 +7642,10 @@ function spawnDetached(command, args) {
|
|
|
7601
7642
|
child.on("error", () => {
|
|
7602
7643
|
});
|
|
7603
7644
|
child.unref();
|
|
7645
|
+
return child.pid ?? null;
|
|
7604
7646
|
}
|
|
7605
7647
|
function openChromiumApp(binaryPath, url2, sizeArgs) {
|
|
7606
|
-
spawnDetached(binaryPath, [`--app=${url2}`, ...sizeArgs]);
|
|
7648
|
+
return spawnDetached(binaryPath, [`--app=${url2}`, ...sizeArgs]);
|
|
7607
7649
|
}
|
|
7608
7650
|
function openOsDefault(url2) {
|
|
7609
7651
|
const platform2 = os7.platform();
|
|
@@ -7630,7 +7672,7 @@ function shouldDisableChromelessWindows() {
|
|
|
7630
7672
|
function openChromelessWindow(url2) {
|
|
7631
7673
|
if (shouldDisableChromelessWindows()) {
|
|
7632
7674
|
openOsDefault(url2);
|
|
7633
|
-
return;
|
|
7675
|
+
return null;
|
|
7634
7676
|
}
|
|
7635
7677
|
const browsers = getChromiumBrowsers();
|
|
7636
7678
|
const defaultId = getDefaultBrowserId();
|
|
@@ -7639,22 +7681,21 @@ function openChromelessWindow(url2) {
|
|
|
7639
7681
|
if (defaultChromium) {
|
|
7640
7682
|
const binary = findExistingBinary(defaultChromium.binaryPaths);
|
|
7641
7683
|
if (binary) {
|
|
7642
|
-
openChromiumApp(binary, url2, sizeArgs);
|
|
7643
|
-
return;
|
|
7684
|
+
return openChromiumApp(binary, url2, sizeArgs);
|
|
7644
7685
|
}
|
|
7645
7686
|
}
|
|
7646
7687
|
if (defaultId !== null && defaultChromium === null) {
|
|
7647
7688
|
openOsDefault(url2);
|
|
7648
|
-
return;
|
|
7689
|
+
return null;
|
|
7649
7690
|
}
|
|
7650
7691
|
for (const browser of browsers) {
|
|
7651
7692
|
const binary = findExistingBinary(browser.binaryPaths);
|
|
7652
7693
|
if (binary) {
|
|
7653
|
-
openChromiumApp(binary, url2, sizeArgs);
|
|
7654
|
-
return;
|
|
7694
|
+
return openChromiumApp(binary, url2, sizeArgs);
|
|
7655
7695
|
}
|
|
7656
7696
|
}
|
|
7657
7697
|
openOsDefault(url2);
|
|
7698
|
+
return null;
|
|
7658
7699
|
}
|
|
7659
7700
|
|
|
7660
7701
|
// ../bitfab-plugin-lib/dist/commands/createStudioSession.js
|
|
@@ -7678,10 +7719,14 @@ async function createStudioSession({ serviceUrl, apiKey, sessionId, initialPath
|
|
|
7678
7719
|
sessionId,
|
|
7679
7720
|
extraHeaders: clientHeaders
|
|
7680
7721
|
});
|
|
7681
|
-
writeActiveStudioSession({ sessionId, serviceUrl });
|
|
7682
7722
|
const separator = initialPath.includes("?") ? "&" : "?";
|
|
7683
7723
|
const url2 = `${serviceUrl}${initialPath}${separator}session=${encodeURIComponent(sessionId)}`;
|
|
7684
|
-
openChromelessWindow(url2);
|
|
7724
|
+
const windowPid = openChromelessWindow(url2);
|
|
7725
|
+
writeActiveStudioSession({
|
|
7726
|
+
sessionId,
|
|
7727
|
+
serviceUrl,
|
|
7728
|
+
windowPid: windowPid ?? void 0
|
|
7729
|
+
});
|
|
7685
7730
|
return { sessionId, serviceUrl };
|
|
7686
7731
|
}
|
|
7687
7732
|
|
|
@@ -7887,7 +7932,7 @@ async function waitForSessionReady(opts) {
|
|
|
7887
7932
|
}
|
|
7888
7933
|
|
|
7889
7934
|
// ../bitfab-plugin-lib/dist/commands/openStudioTo.js
|
|
7890
|
-
var
|
|
7935
|
+
var WINDOW_CLOSE_GRACE_PERIOD_MS = 1e4;
|
|
7891
7936
|
var LOGIN_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
7892
7937
|
var StudioNavigationError = class extends Error {
|
|
7893
7938
|
reason;
|
|
@@ -7914,7 +7959,7 @@ async function openStudioTo(path15, opts = {}) {
|
|
|
7914
7959
|
const redirectPath = `${path15}${separator}session=${encodeURIComponent(sessionId2)}&pluginLogin=true`;
|
|
7915
7960
|
const signInPath = `/studio/sign-in?redirect_url=${encodeURIComponent(redirectPath)}`;
|
|
7916
7961
|
const signInUrl = `${serviceUrl}${signInPath}&session=${encodeURIComponent(sessionId2)}`;
|
|
7917
|
-
openChromelessWindow(signInUrl);
|
|
7962
|
+
const windowPid = openChromelessWindow(signInUrl);
|
|
7918
7963
|
opts.onLoginRequired?.(sessionId2, signInUrl);
|
|
7919
7964
|
const abortController = new AbortController();
|
|
7920
7965
|
const timer = setTimeout(() => abortController.abort(), LOGIN_TIMEOUT_MS);
|
|
@@ -7941,7 +7986,11 @@ async function openStudioTo(path15, opts = {}) {
|
|
|
7941
7986
|
apiKey: loginApiKey,
|
|
7942
7987
|
sessionId: sessionId2
|
|
7943
7988
|
});
|
|
7944
|
-
writeActiveStudioSession({
|
|
7989
|
+
writeActiveStudioSession({
|
|
7990
|
+
sessionId: sessionId2,
|
|
7991
|
+
serviceUrl,
|
|
7992
|
+
windowPid: windowPid ?? void 0
|
|
7993
|
+
});
|
|
7945
7994
|
const poller2 = startEventPoller(serviceUrl, loginApiKey, sessionId2, opts.onEvent, restoreFocus);
|
|
7946
7995
|
return {
|
|
7947
7996
|
sessionId: sessionId2,
|
|
@@ -8001,71 +8050,73 @@ async function openStudioTo(path15, opts = {}) {
|
|
|
8001
8050
|
function startEventPoller(serviceUrl, apiKey, sessionId, onEvent, restoreFocus) {
|
|
8002
8051
|
const abortController = new AbortController();
|
|
8003
8052
|
let graceTimer = null;
|
|
8004
|
-
let
|
|
8053
|
+
let endedNotified = false;
|
|
8005
8054
|
const clearGraceTimer = () => {
|
|
8006
8055
|
if (graceTimer) {
|
|
8007
8056
|
clearTimeout(graceTimer);
|
|
8008
8057
|
graceTimer = null;
|
|
8009
8058
|
}
|
|
8010
8059
|
};
|
|
8011
|
-
const
|
|
8012
|
-
|
|
8013
|
-
|
|
8014
|
-
|
|
8060
|
+
const notifyEnded = (baseEvent) => {
|
|
8061
|
+
if (!endedNotified) {
|
|
8062
|
+
endedNotified = true;
|
|
8063
|
+
restoreFocus();
|
|
8064
|
+
onEvent({ ...baseEvent, type: "studio:session-ended" });
|
|
8015
8065
|
}
|
|
8016
|
-
restoreFocus();
|
|
8017
|
-
onEvent({ ...baseEvent, type: "studio:session-ended" });
|
|
8018
8066
|
abortController.abort();
|
|
8019
8067
|
};
|
|
8020
|
-
const done =
|
|
8021
|
-
serviceUrl,
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
|
|
8068
|
+
const done = (async () => {
|
|
8069
|
+
const startCursor = await fetchStreamTip({ serviceUrl, apiKey, sessionId });
|
|
8070
|
+
if (abortController.signal.aborted) {
|
|
8071
|
+
return;
|
|
8072
|
+
}
|
|
8073
|
+
await pollAgentSessionEvents({
|
|
8074
|
+
serviceUrl,
|
|
8075
|
+
apiKey,
|
|
8076
|
+
sessionId,
|
|
8077
|
+
startCursor,
|
|
8078
|
+
abortSignal: abortController.signal,
|
|
8079
|
+
onEvent: (event) => {
|
|
8080
|
+
if (event.type === "studio:window-closed") {
|
|
8081
|
+
clearGraceTimer();
|
|
8031
8082
|
graceTimer = setTimeout(() => {
|
|
8032
|
-
|
|
8033
|
-
|
|
8083
|
+
graceTimer = null;
|
|
8084
|
+
clearActiveStudioSession(sessionId);
|
|
8085
|
+
notifyEnded(event);
|
|
8086
|
+
}, WINDOW_CLOSE_GRACE_PERIOD_MS);
|
|
8087
|
+
return;
|
|
8034
8088
|
}
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8089
|
+
if (event.type === "studio:session-started") {
|
|
8090
|
+
if (graceTimer) {
|
|
8091
|
+
clearGraceTimer();
|
|
8092
|
+
console.error("Studio reconnected after page refresh");
|
|
8093
|
+
}
|
|
8094
|
+
return;
|
|
8095
|
+
}
|
|
8096
|
+
if (event.type === "studio:session-ended") {
|
|
8039
8097
|
clearGraceTimer();
|
|
8040
|
-
|
|
8098
|
+
notifyEnded(event);
|
|
8099
|
+
return;
|
|
8041
8100
|
}
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8101
|
+
if (event.type === "studio:session-closed") {
|
|
8102
|
+
return;
|
|
8103
|
+
}
|
|
8104
|
+
if (event.type === "studio:not-member") {
|
|
8105
|
+
restoreFocus();
|
|
8106
|
+
onEvent(event);
|
|
8107
|
+
abortController.abort();
|
|
8108
|
+
return;
|
|
8109
|
+
}
|
|
8110
|
+
if (event.type === "studio:return-to-agent" || event.type === "studio:edit-with-agent") {
|
|
8111
|
+
restoreFocus();
|
|
8051
8112
|
}
|
|
8052
|
-
return;
|
|
8053
|
-
}
|
|
8054
|
-
if (event.type === "studio:not-member") {
|
|
8055
|
-
restoreFocus();
|
|
8056
8113
|
onEvent(event);
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
if (event.type === "studio:return-to-agent" || event.type === "studio:edit-with-agent") {
|
|
8061
|
-
restoreFocus();
|
|
8114
|
+
},
|
|
8115
|
+
onError: (err) => {
|
|
8116
|
+
console.error(`studio agent-event poll: ${err.message}`);
|
|
8062
8117
|
}
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
onError: (err) => {
|
|
8066
|
-
console.error(`studio agent-event poll: ${err.message}`);
|
|
8067
|
-
}
|
|
8068
|
-
}).catch((err) => {
|
|
8118
|
+
});
|
|
8119
|
+
})().catch((err) => {
|
|
8069
8120
|
if (!abortController.signal.aborted) {
|
|
8070
8121
|
console.error(`studio agent-event poll terminated: ${err.message}`);
|
|
8071
8122
|
}
|