@withbuddi/buddi 0.1.0-pre.38 → 0.1.0-pre.40
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/extension/background.js +635 -21
- package/extension/manifest.json +2 -2
- package/node_modules/@buddi/cli/dist/.tsbuildinfo +1 -1
- package/node_modules/@buddi/cli/dist/access-cmd.d.ts +1 -1
- package/node_modules/@buddi/cli/dist/access-cmd.d.ts.map +1 -1
- package/node_modules/@buddi/cli/dist/access-cmd.js +23 -3
- package/node_modules/@buddi/cli/dist/access-cmd.js.map +1 -1
- package/node_modules/@buddi/cli/dist/args.d.ts +2 -2
- package/node_modules/@buddi/cli/dist/args.d.ts.map +1 -1
- package/node_modules/@buddi/cli/dist/args.js +7 -2
- package/node_modules/@buddi/cli/dist/args.js.map +1 -1
- package/node_modules/@buddi/cli/dist/commands.d.ts.map +1 -1
- package/node_modules/@buddi/cli/dist/commands.js +11 -0
- package/node_modules/@buddi/cli/dist/commands.js.map +1 -1
- package/node_modules/@buddi/cli/dist/main.d.ts.map +1 -1
- package/node_modules/@buddi/cli/dist/main.js +2 -1
- package/node_modules/@buddi/cli/dist/main.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/.tsbuildinfo +1 -1
- package/node_modules/@buddi/gateway/dist/missions/ask-cap.d.ts +26 -0
- package/node_modules/@buddi/gateway/dist/missions/ask-cap.d.ts.map +1 -0
- package/node_modules/@buddi/gateway/dist/missions/ask-cap.js +83 -0
- package/node_modules/@buddi/gateway/dist/missions/ask-cap.js.map +1 -0
- package/node_modules/@buddi/gateway/dist/missions/execute.d.ts +12 -1
- package/node_modules/@buddi/gateway/dist/missions/execute.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/missions/execute.js +73 -11
- package/node_modules/@buddi/gateway/dist/missions/execute.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/missions/parked.d.ts +6 -0
- package/node_modules/@buddi/gateway/dist/missions/parked.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/missions/parked.js +7 -1
- package/node_modules/@buddi/gateway/dist/missions/parked.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/serve.js +1 -1
- package/node_modules/@buddi/gateway/dist/serve.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/system-context.d.ts +7 -0
- package/node_modules/@buddi/gateway/dist/system-context.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/system-context.js +8 -0
- package/node_modules/@buddi/gateway/dist/system-context.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/api-routes.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/api-routes.js +11 -3
- package/node_modules/@buddi/gateway/dist/web/api-routes.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/extension.d.ts +13 -1
- package/node_modules/@buddi/gateway/dist/web/extension.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/extension.js +125 -5
- package/node_modules/@buddi/gateway/dist/web/extension.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/onboarding.d.ts +22 -4
- package/node_modules/@buddi/gateway/dist/web/onboarding.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/onboarding.js +82 -13
- package/node_modules/@buddi/gateway/dist/web/onboarding.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/remote-hand.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/remote-hand.js +45 -2
- package/node_modules/@buddi/gateway/dist/web/remote-hand.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/secrets.d.ts +26 -1
- package/node_modules/@buddi/gateway/dist/web/secrets.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/secrets.js +66 -3
- package/node_modules/@buddi/gateway/dist/web/secrets.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/server.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/server.js +70 -11
- package/node_modules/@buddi/gateway/dist/web/server.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/version.d.ts +2 -0
- package/node_modules/@buddi/gateway/dist/web/version.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/version.js +5 -1
- package/node_modules/@buddi/gateway/dist/web/version.js.map +1 -1
- package/node_modules/@buddi/install/dist/.tsbuildinfo +1 -1
- package/node_modules/@buddi/install/dist/app-layout.d.ts +127 -0
- package/node_modules/@buddi/install/dist/app-layout.d.ts.map +1 -0
- package/node_modules/@buddi/install/dist/app-layout.js +323 -0
- package/node_modules/@buddi/install/dist/app-layout.js.map +1 -0
- package/node_modules/@buddi/install/dist/index.d.ts +1 -0
- package/node_modules/@buddi/install/dist/index.d.ts.map +1 -1
- package/node_modules/@buddi/install/dist/index.js +1 -0
- package/node_modules/@buddi/install/dist/index.js.map +1 -1
- package/node_modules/@buddi/install/dist/launcher.d.ts.map +1 -1
- package/node_modules/@buddi/install/dist/launcher.js +18 -4
- package/node_modules/@buddi/install/dist/launcher.js.map +1 -1
- package/node_modules/@buddi/install/dist/provenance.d.ts +34 -0
- package/node_modules/@buddi/install/dist/provenance.d.ts.map +1 -0
- package/node_modules/@buddi/install/dist/provenance.js +200 -0
- package/node_modules/@buddi/install/dist/provenance.js.map +1 -0
- package/node_modules/@buddi/install/dist/supervisor.d.ts.map +1 -1
- package/node_modules/@buddi/install/dist/supervisor.js +9 -4
- package/node_modules/@buddi/install/dist/supervisor.js.map +1 -1
- package/node_modules/@buddi/install/dist/upgrade.d.ts +77 -3
- package/node_modules/@buddi/install/dist/upgrade.d.ts.map +1 -1
- package/node_modules/@buddi/install/dist/upgrade.js +325 -43
- package/node_modules/@buddi/install/dist/upgrade.js.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/.tsbuildinfo +1 -1
- package/node_modules/@buddi/tool-browser/dist/controller.d.ts +6 -0
- package/node_modules/@buddi/tool-browser/dist/controller.d.ts.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/controller.js +80 -6
- package/node_modules/@buddi/tool-browser/dist/controller.js.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/driver.d.ts +3 -1
- package/node_modules/@buddi/tool-browser/dist/driver.d.ts.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/driver.js +82 -1
- package/node_modules/@buddi/tool-browser/dist/driver.js.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/extension.d.ts +60 -7
- package/node_modules/@buddi/tool-browser/dist/extension.d.ts.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/extension.js +90 -13
- package/node_modules/@buddi/tool-browser/dist/extension.js.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/index.d.ts +6 -4
- package/node_modules/@buddi/tool-browser/dist/index.d.ts.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/index.js +7 -4
- package/node_modules/@buddi/tool-browser/dist/index.js.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/logins.d.ts +150 -0
- package/node_modules/@buddi/tool-browser/dist/logins.d.ts.map +1 -0
- package/node_modules/@buddi/tool-browser/dist/logins.js +476 -0
- package/node_modules/@buddi/tool-browser/dist/logins.js.map +1 -0
- package/node_modules/@buddi/tool-browser/dist/routes.d.ts +22 -1
- package/node_modules/@buddi/tool-browser/dist/routes.d.ts.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/routes.js +51 -3
- package/node_modules/@buddi/tool-browser/dist/routes.js.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/secrets.d.ts +3 -1
- package/node_modules/@buddi/tool-browser/dist/secrets.d.ts.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/secrets.js +10 -6
- package/node_modules/@buddi/tool-browser/dist/secrets.js.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/service.d.ts +26 -2
- package/node_modules/@buddi/tool-browser/dist/service.d.ts.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/service.js +117 -3
- package/node_modules/@buddi/tool-browser/dist/service.js.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/types.d.ts +88 -0
- package/node_modules/@buddi/tool-browser/dist/types.d.ts.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/types.js +18 -0
- package/node_modules/@buddi/tool-browser/dist/types.js.map +1 -1
- package/node_modules/hono/dist/cjs/hono-base.js +2 -0
- package/node_modules/hono/dist/cjs/middleware/language/language.js +1 -1
- package/node_modules/hono/dist/cjs/middleware/mount/index.js +71 -0
- package/node_modules/hono/dist/cjs/request.js +4 -4
- package/node_modules/hono/dist/hono-base.js +2 -0
- package/node_modules/hono/dist/middleware/mount/index.js +70 -0
- package/node_modules/hono/dist/request.js +4 -4
- package/node_modules/hono/dist/types/hono-base.d.ts +2 -0
- package/node_modules/hono/dist/types/jsx/base.d.ts +1 -1
- package/node_modules/hono/dist/types/middleware/mount/index.d.ts +46 -0
- package/node_modules/hono/dist/types/request.d.ts +4 -4
- package/node_modules/hono/package.json +9 -1
- package/package.json +2 -2
- package/packages/cli/dist/.tsbuildinfo +1 -1
- package/packages/cli/dist/access-cmd.d.ts +1 -1
- package/packages/cli/dist/access-cmd.d.ts.map +1 -1
- package/packages/cli/dist/access-cmd.js +23 -3
- package/packages/cli/dist/access-cmd.js.map +1 -1
- package/packages/cli/dist/args.d.ts +2 -2
- package/packages/cli/dist/args.d.ts.map +1 -1
- package/packages/cli/dist/args.js +7 -2
- package/packages/cli/dist/args.js.map +1 -1
- package/packages/cli/dist/commands.d.ts.map +1 -1
- package/packages/cli/dist/commands.js +11 -0
- package/packages/cli/dist/commands.js.map +1 -1
- package/packages/cli/dist/main.d.ts.map +1 -1
- package/packages/cli/dist/main.js +2 -1
- package/packages/cli/dist/main.js.map +1 -1
- package/packages/gateway/dist/.tsbuildinfo +1 -1
- package/packages/gateway/dist/missions/ask-cap.d.ts +26 -0
- package/packages/gateway/dist/missions/ask-cap.d.ts.map +1 -0
- package/packages/gateway/dist/missions/ask-cap.js +83 -0
- package/packages/gateway/dist/missions/ask-cap.js.map +1 -0
- package/packages/gateway/dist/missions/execute.d.ts +12 -1
- package/packages/gateway/dist/missions/execute.d.ts.map +1 -1
- package/packages/gateway/dist/missions/execute.js +73 -11
- package/packages/gateway/dist/missions/execute.js.map +1 -1
- package/packages/gateway/dist/missions/parked.d.ts +6 -0
- package/packages/gateway/dist/missions/parked.d.ts.map +1 -1
- package/packages/gateway/dist/missions/parked.js +7 -1
- package/packages/gateway/dist/missions/parked.js.map +1 -1
- package/packages/gateway/dist/serve.js +1 -1
- package/packages/gateway/dist/serve.js.map +1 -1
- package/packages/gateway/dist/system-context.d.ts +7 -0
- package/packages/gateway/dist/system-context.d.ts.map +1 -1
- package/packages/gateway/dist/system-context.js +8 -0
- package/packages/gateway/dist/system-context.js.map +1 -1
- package/packages/gateway/dist/web/api-routes.d.ts.map +1 -1
- package/packages/gateway/dist/web/api-routes.js +11 -3
- package/packages/gateway/dist/web/api-routes.js.map +1 -1
- package/packages/gateway/dist/web/extension.d.ts +13 -1
- package/packages/gateway/dist/web/extension.d.ts.map +1 -1
- package/packages/gateway/dist/web/extension.js +125 -5
- package/packages/gateway/dist/web/extension.js.map +1 -1
- package/packages/gateway/dist/web/onboarding.d.ts +22 -4
- package/packages/gateway/dist/web/onboarding.d.ts.map +1 -1
- package/packages/gateway/dist/web/onboarding.js +82 -13
- package/packages/gateway/dist/web/onboarding.js.map +1 -1
- package/packages/gateway/dist/web/remote-hand.d.ts.map +1 -1
- package/packages/gateway/dist/web/remote-hand.js +45 -2
- package/packages/gateway/dist/web/remote-hand.js.map +1 -1
- package/packages/gateway/dist/web/secrets.d.ts +26 -1
- package/packages/gateway/dist/web/secrets.d.ts.map +1 -1
- package/packages/gateway/dist/web/secrets.js +66 -3
- package/packages/gateway/dist/web/secrets.js.map +1 -1
- package/packages/gateway/dist/web/server.d.ts.map +1 -1
- package/packages/gateway/dist/web/server.js +70 -11
- package/packages/gateway/dist/web/server.js.map +1 -1
- package/packages/gateway/dist/web/version.d.ts +2 -0
- package/packages/gateway/dist/web/version.d.ts.map +1 -1
- package/packages/gateway/dist/web/version.js +5 -1
- package/packages/gateway/dist/web/version.js.map +1 -1
- package/packages/install/dist/.tsbuildinfo +1 -1
- package/packages/install/dist/app-layout.d.ts +127 -0
- package/packages/install/dist/app-layout.d.ts.map +1 -0
- package/packages/install/dist/app-layout.js +323 -0
- package/packages/install/dist/app-layout.js.map +1 -0
- package/packages/install/dist/index.d.ts +1 -0
- package/packages/install/dist/index.d.ts.map +1 -1
- package/packages/install/dist/index.js +1 -0
- package/packages/install/dist/index.js.map +1 -1
- package/packages/install/dist/launcher.d.ts.map +1 -1
- package/packages/install/dist/launcher.js +18 -4
- package/packages/install/dist/launcher.js.map +1 -1
- package/packages/install/dist/provenance.d.ts +34 -0
- package/packages/install/dist/provenance.d.ts.map +1 -0
- package/packages/install/dist/provenance.js +200 -0
- package/packages/install/dist/provenance.js.map +1 -0
- package/packages/install/dist/supervisor.d.ts.map +1 -1
- package/packages/install/dist/supervisor.js +9 -4
- package/packages/install/dist/supervisor.js.map +1 -1
- package/packages/install/dist/upgrade.d.ts +77 -3
- package/packages/install/dist/upgrade.d.ts.map +1 -1
- package/packages/install/dist/upgrade.js +325 -43
- package/packages/install/dist/upgrade.js.map +1 -1
- package/packages/tools/browser/dist/.tsbuildinfo +1 -1
- package/packages/tools/browser/dist/controller.d.ts +6 -0
- package/packages/tools/browser/dist/controller.d.ts.map +1 -1
- package/packages/tools/browser/dist/controller.js +80 -6
- package/packages/tools/browser/dist/controller.js.map +1 -1
- package/packages/tools/browser/dist/driver.d.ts +3 -1
- package/packages/tools/browser/dist/driver.d.ts.map +1 -1
- package/packages/tools/browser/dist/driver.js +82 -1
- package/packages/tools/browser/dist/driver.js.map +1 -1
- package/packages/tools/browser/dist/extension.d.ts +60 -7
- package/packages/tools/browser/dist/extension.d.ts.map +1 -1
- package/packages/tools/browser/dist/extension.js +90 -13
- package/packages/tools/browser/dist/extension.js.map +1 -1
- package/packages/tools/browser/dist/index.d.ts +6 -4
- package/packages/tools/browser/dist/index.d.ts.map +1 -1
- package/packages/tools/browser/dist/index.js +7 -4
- package/packages/tools/browser/dist/index.js.map +1 -1
- package/packages/tools/browser/dist/logins.d.ts +150 -0
- package/packages/tools/browser/dist/logins.d.ts.map +1 -0
- package/packages/tools/browser/dist/logins.js +476 -0
- package/packages/tools/browser/dist/logins.js.map +1 -0
- package/packages/tools/browser/dist/routes.d.ts +22 -1
- package/packages/tools/browser/dist/routes.d.ts.map +1 -1
- package/packages/tools/browser/dist/routes.js +51 -3
- package/packages/tools/browser/dist/routes.js.map +1 -1
- package/packages/tools/browser/dist/secrets.d.ts +3 -1
- package/packages/tools/browser/dist/secrets.d.ts.map +1 -1
- package/packages/tools/browser/dist/secrets.js +10 -6
- package/packages/tools/browser/dist/secrets.js.map +1 -1
- package/packages/tools/browser/dist/service.d.ts +26 -2
- package/packages/tools/browser/dist/service.d.ts.map +1 -1
- package/packages/tools/browser/dist/service.js +117 -3
- package/packages/tools/browser/dist/service.js.map +1 -1
- package/packages/tools/browser/dist/types.d.ts +88 -0
- package/packages/tools/browser/dist/types.d.ts.map +1 -1
- package/packages/tools/browser/dist/types.js +18 -0
- package/packages/tools/browser/dist/types.js.map +1 -1
- package/packages/web/dist/assets/index-CO4OawAX.js +146 -0
- package/packages/web/dist/assets/index-DYfeUW-0.css +1 -0
- package/packages/web/dist/assets/{lottie_light-CuiSMqnl.js → lottie_light-DB7hai8n.js} +1 -1
- package/packages/web/dist/build.json +1 -1
- package/packages/web/dist/index.html +2 -2
- package/packages/web/dist/precache.json +4 -4
- package/packages/web/dist/assets/index-Bac-CdD-.css +0 -1
- package/packages/web/dist/assets/index-DI8bp_D7.js +0 -146
package/extension/background.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
// src/protocol.ts
|
|
2
2
|
var COMMAND_NAMES = ["navigate", "observe", "click", "fill", "select", "press", "scroll", "tab", "close", "fieldInfo", "secretFill"];
|
|
3
|
-
var HAND_COMMANDS = ["screencast.start", "screencast.stop", "input"];
|
|
3
|
+
var HAND_COMMANDS = ["screencast.start", "screencast.stop", "input", "hold", "unhold"];
|
|
4
4
|
var PreconditionError = class extends Error {
|
|
5
5
|
};
|
|
6
|
+
var OpenedError = class extends Error {
|
|
7
|
+
constructor(message, page) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.page = page;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
6
12
|
var CancelledError = class extends Error {
|
|
7
13
|
};
|
|
8
14
|
var Cancellation = class {
|
|
@@ -148,6 +154,18 @@ var Protocol = class {
|
|
|
148
154
|
case "command":
|
|
149
155
|
await this.#command(message);
|
|
150
156
|
return;
|
|
157
|
+
case "loginAck": {
|
|
158
|
+
const id = message["id"];
|
|
159
|
+
if (!this.#authenticated || typeof id !== "string" || id.length > 64) return;
|
|
160
|
+
const ask = message["ask"];
|
|
161
|
+
if (ask === "save" || ask === "update" || ask === "none") {
|
|
162
|
+
this.#options.onLoginAck?.(id, { ask });
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const reason = typeof message["reason"] === "string" ? message["reason"].slice(0, 300) : "";
|
|
166
|
+
this.#options.onLoginAck?.(id, message["saved"] === true ? { saved: true } : { saved: false, reason: reason || "buddi could not keep that login." });
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
151
169
|
default:
|
|
152
170
|
return;
|
|
153
171
|
}
|
|
@@ -212,7 +230,14 @@ var Protocol = class {
|
|
|
212
230
|
if (error instanceof CancelledError) {
|
|
213
231
|
this.#options.send({ type: "result", id, ok: false, error: "cancelled", precondition: !cancel.dispatched });
|
|
214
232
|
} else {
|
|
215
|
-
this.#options.send({
|
|
233
|
+
this.#options.send({
|
|
234
|
+
type: "result",
|
|
235
|
+
id,
|
|
236
|
+
ok: false,
|
|
237
|
+
error: sentence(error),
|
|
238
|
+
precondition: error instanceof PreconditionError,
|
|
239
|
+
...error instanceof OpenedError ? { page: error.page } : {}
|
|
240
|
+
});
|
|
216
241
|
}
|
|
217
242
|
} finally {
|
|
218
243
|
this.#running.delete(id);
|
|
@@ -300,6 +325,397 @@ function hideBar() {
|
|
|
300
325
|
const holder = globalThis;
|
|
301
326
|
delete holder["__buddiBar"];
|
|
302
327
|
}
|
|
328
|
+
var GIVE_BACK_MESSAGE = "buddi-give-back";
|
|
329
|
+
function showHeldBar(session) {
|
|
330
|
+
const doc = globalThis.document;
|
|
331
|
+
if (!doc || doc.getElementById("buddi-held-bar")) return;
|
|
332
|
+
const host = doc.createElement("div");
|
|
333
|
+
host.id = "buddi-held-bar";
|
|
334
|
+
host.setAttribute("style", "position:fixed;top:0;left:0;right:0;z-index:2147483647;all:initial;");
|
|
335
|
+
const root = host.attachShadow({ mode: "closed" });
|
|
336
|
+
const bar = doc.createElement("div");
|
|
337
|
+
bar.setAttribute("role", "status");
|
|
338
|
+
bar.setAttribute("style", "position:fixed;top:0;left:0;right:0;display:flex;gap:12px;align-items:center;justify-content:flex-end;padding:6px 16px;font:13px/1.4 system-ui,-apple-system,sans-serif;background:#1f2a44;color:#fff;box-shadow:0 1px 4px rgba(0,0,0,.25);");
|
|
339
|
+
const label = doc.createElement("span");
|
|
340
|
+
label.textContent = "buddi is waiting";
|
|
341
|
+
label.setAttribute("style", "margin-right:auto;");
|
|
342
|
+
const give = doc.createElement("button");
|
|
343
|
+
give.type = "button";
|
|
344
|
+
give.textContent = "Give it back";
|
|
345
|
+
give.setAttribute("style", "font:inherit;cursor:pointer;border-radius:6px;padding:3px 10px;border:1px solid #fff;background:#fff;color:#1f2a44;");
|
|
346
|
+
give.addEventListener("click", () => {
|
|
347
|
+
const runtime = globalThis.chrome?.runtime;
|
|
348
|
+
void runtime?.sendMessage({ type: "buddi-give-back", session })?.catch?.(() => void 0);
|
|
349
|
+
host.remove();
|
|
350
|
+
});
|
|
351
|
+
bar.append(label, give);
|
|
352
|
+
root.append(bar);
|
|
353
|
+
(doc.documentElement ?? doc.body).append(host);
|
|
354
|
+
const asking = [];
|
|
355
|
+
const restore = () => {
|
|
356
|
+
for (const button of asking.splice(0)) button.remove();
|
|
357
|
+
label.textContent = "buddi is waiting";
|
|
358
|
+
give.style.display = "";
|
|
359
|
+
};
|
|
360
|
+
globalThis["__buddiHeldAsk"] = (question, choices) => {
|
|
361
|
+
restore();
|
|
362
|
+
label.textContent = question;
|
|
363
|
+
give.style.display = "none";
|
|
364
|
+
for (const choice of choices) {
|
|
365
|
+
const b = doc.createElement("button");
|
|
366
|
+
b.type = "button";
|
|
367
|
+
b.textContent = choice.label;
|
|
368
|
+
b.setAttribute("style", `font:inherit;cursor:pointer;border-radius:6px;padding:3px 10px;border:1px solid #fff;${choice.primary ? "background:#fff;color:#1f2a44;" : "background:transparent;color:#fff;"}`);
|
|
369
|
+
b.addEventListener("click", () => {
|
|
370
|
+
restore();
|
|
371
|
+
choice.pick();
|
|
372
|
+
});
|
|
373
|
+
asking.push(b);
|
|
374
|
+
bar.insertBefore(b, give);
|
|
375
|
+
}
|
|
376
|
+
return restore;
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
function hideHeldBar() {
|
|
380
|
+
const doc = globalThis.document;
|
|
381
|
+
doc?.getElementById("buddi-held-bar")?.remove();
|
|
382
|
+
delete globalThis["__buddiHeldAsk"];
|
|
383
|
+
}
|
|
384
|
+
var LOGIN_MESSAGE = "buddi-login";
|
|
385
|
+
var LOGIN_PROMPT_MS = 2 * 6e4;
|
|
386
|
+
function watchHeldLogins(session, facts) {
|
|
387
|
+
const holder = globalThis;
|
|
388
|
+
const known = { never: [...facts?.never ?? []], saved: [...facts?.saved ?? []] };
|
|
389
|
+
const existing = holder["__buddiHeldLogins"];
|
|
390
|
+
if (existing) {
|
|
391
|
+
existing.session = session;
|
|
392
|
+
existing.facts = known;
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const doc = globalThis.document;
|
|
396
|
+
if (!doc) return;
|
|
397
|
+
const state = { session, facts: known, last: "", lastAt: 0, typedAt: 0, cached: void 0, timer: void 0, drop: void 0 };
|
|
398
|
+
holder["__buddiHeldLogins"] = state;
|
|
399
|
+
const runtime = () => globalThis.chrome?.runtime;
|
|
400
|
+
const call = async (message) => {
|
|
401
|
+
try {
|
|
402
|
+
return await runtime()?.sendMessage(message);
|
|
403
|
+
} catch {
|
|
404
|
+
return void 0;
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
const site = () => (globalThis.location?.hostname ?? "").toLowerCase().replace(/^www\./, "");
|
|
408
|
+
const listed = (where) => state.facts.never.some((entry) => {
|
|
409
|
+
const bare = entry.toLowerCase().replace(/^www\./, "");
|
|
410
|
+
return where === bare || where.endsWith(`.${bare}`);
|
|
411
|
+
});
|
|
412
|
+
const bar = () => {
|
|
413
|
+
const question = holder["__buddiHeldAsk"];
|
|
414
|
+
return typeof question === "function" ? question : void 0;
|
|
415
|
+
};
|
|
416
|
+
const prompt = (where, update) => {
|
|
417
|
+
const question = bar();
|
|
418
|
+
if (!question) return;
|
|
419
|
+
if (state.drop) clearTimeout(state.drop);
|
|
420
|
+
let answered = false;
|
|
421
|
+
const save = () => {
|
|
422
|
+
if (answered) return;
|
|
423
|
+
answered = true;
|
|
424
|
+
const asking = bar();
|
|
425
|
+
asking?.(update ? "Updating\u2026" : "Saving\u2026", []);
|
|
426
|
+
void call({ type: "buddi-login", session: state.session, decision: "save" }).then((reply) => {
|
|
427
|
+
const answer = reply;
|
|
428
|
+
const now = bar();
|
|
429
|
+
if (!now) return;
|
|
430
|
+
if (answer?.saved === true) {
|
|
431
|
+
const restore2 = now(update ? `Updated the login for ${where}` : `Saved the login for ${where}`, []);
|
|
432
|
+
if (state.drop) clearTimeout(state.drop);
|
|
433
|
+
state.drop = setTimeout(restore2, 2e3);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
const reason = typeof answer?.reason === "string" && answer.reason !== "" ? answer.reason : "buddi did not answer. Try again.";
|
|
437
|
+
now(reason, [
|
|
438
|
+
{ label: "Try again", primary: true, pick: () => {
|
|
439
|
+
answered = false;
|
|
440
|
+
save();
|
|
441
|
+
} },
|
|
442
|
+
{ label: "Not now", pick: () => {
|
|
443
|
+
void call({ type: "buddi-login", session: state.session, decision: "later" });
|
|
444
|
+
} }
|
|
445
|
+
]);
|
|
446
|
+
});
|
|
447
|
+
};
|
|
448
|
+
const restore = question(update ? `Update the login for ${where}?` : `Save this login for ${where}?`, [
|
|
449
|
+
{ label: update ? "Update" : "Save", primary: true, pick: save },
|
|
450
|
+
{ label: "Not now", pick: () => {
|
|
451
|
+
answered = true;
|
|
452
|
+
void call({ type: "buddi-login", session: state.session, decision: "later" });
|
|
453
|
+
} },
|
|
454
|
+
{ label: "Never for this site", pick: () => {
|
|
455
|
+
answered = true;
|
|
456
|
+
state.facts.never.push(where);
|
|
457
|
+
void call({ type: "buddi-login", session: state.session, decision: "never" });
|
|
458
|
+
} }
|
|
459
|
+
]);
|
|
460
|
+
state.drop = setTimeout(() => {
|
|
461
|
+
if (!answered) restore();
|
|
462
|
+
}, 2 * 6e4);
|
|
463
|
+
};
|
|
464
|
+
const ask = (found) => {
|
|
465
|
+
const where = site();
|
|
466
|
+
if (where === "" || listed(where) || !bar()) return;
|
|
467
|
+
const update = state.facts.saved.some((login) => login.site === where && login.username === found.username);
|
|
468
|
+
void call({ type: "buddi-login-seen", session: state.session, username: found.username, password: found.password, update }).then((reply) => {
|
|
469
|
+
const asked = reply;
|
|
470
|
+
if (holder["__buddiHeldLogins"] !== state || !asked || typeof asked !== "object") return;
|
|
471
|
+
prompt(where, asked.update === true);
|
|
472
|
+
});
|
|
473
|
+
};
|
|
474
|
+
void call({ type: "buddi-login-pending", session: state.session }).then((reply) => {
|
|
475
|
+
const waiting = reply;
|
|
476
|
+
if (holder["__buddiHeldLogins"] !== state || !waiting || typeof waiting.site !== "string") return;
|
|
477
|
+
prompt(waiting.site, waiting.update === true);
|
|
478
|
+
});
|
|
479
|
+
const NOT_SUBMIT = /\b(show|hide|reveal|toggle|eye|forgot|reset|cancel|back|close|sign ?up|register|create)\b/i;
|
|
480
|
+
const USER = /user|email|login|account|ident|phone|mail/i;
|
|
481
|
+
const passwords = (root) => Array.from(root.querySelectorAll('input[type="password" i]'));
|
|
482
|
+
const usernameFor = (password) => {
|
|
483
|
+
const scope = password.form ?? doc;
|
|
484
|
+
const inputs = Array.from(scope.querySelectorAll("input")).filter((input) => ["text", "email", "tel", ""].includes((input.getAttribute("type") ?? "").toLowerCase()) && input.value.trim() !== "");
|
|
485
|
+
const marked = inputs.find((input) => (input.getAttribute("autocomplete") ?? "").toLowerCase().includes("username") || (input.getAttribute("type") ?? "").toLowerCase() === "email");
|
|
486
|
+
if (marked) return marked.value.trim();
|
|
487
|
+
const before = inputs.filter((input) => (input.compareDocumentPosition(password) & 4) !== 0);
|
|
488
|
+
const pick = before[before.length - 1] ?? inputs.find((input) => USER.test(`${input.name} ${input.id} ${input.getAttribute("autocomplete") ?? ""}`));
|
|
489
|
+
return pick ? pick.value.trim() : "";
|
|
490
|
+
};
|
|
491
|
+
const read = (scope) => {
|
|
492
|
+
const field = passwords(scope ?? doc).find((input) => input.value !== "");
|
|
493
|
+
return field ? { username: usernameFor(field), password: field.value } : void 0;
|
|
494
|
+
};
|
|
495
|
+
const send2 = (found) => {
|
|
496
|
+
if (holder["__buddiHeldLogins"] !== state) return;
|
|
497
|
+
if (!found || found.password === "") return;
|
|
498
|
+
const key = `${found.username}\0${found.password}`;
|
|
499
|
+
const now = Date.now();
|
|
500
|
+
if (key === state.last && now - state.lastAt < 5e3) return;
|
|
501
|
+
state.last = key;
|
|
502
|
+
state.lastAt = now;
|
|
503
|
+
state.cached = void 0;
|
|
504
|
+
ask(found);
|
|
505
|
+
};
|
|
506
|
+
doc.addEventListener("submit", (event) => {
|
|
507
|
+
send2(read(event.target) ?? state.cached);
|
|
508
|
+
}, true);
|
|
509
|
+
doc.addEventListener("keydown", (event) => {
|
|
510
|
+
if (event.key !== "Enter") return;
|
|
511
|
+
const target = event.target;
|
|
512
|
+
if (!target || target.tagName !== "INPUT") return;
|
|
513
|
+
send2(read(target.form ?? doc));
|
|
514
|
+
}, true);
|
|
515
|
+
doc.addEventListener("click", (event) => {
|
|
516
|
+
const target = event.target;
|
|
517
|
+
const button = target?.closest?.('button, input[type="submit" i], input[type="image" i], input[type="button" i], [role="button"]');
|
|
518
|
+
if (!button) return;
|
|
519
|
+
const words = `${button.textContent ?? ""} ${button.getAttribute("aria-label") ?? ""} ${button.getAttribute("value") ?? ""} ${button.getAttribute("title") ?? ""}`;
|
|
520
|
+
if (NOT_SUBMIT.test(words)) return;
|
|
521
|
+
send2(read(button.form ?? doc));
|
|
522
|
+
}, true);
|
|
523
|
+
doc.addEventListener("input", (event) => {
|
|
524
|
+
const target = event.target;
|
|
525
|
+
if ((target?.getAttribute?.("type") ?? "").toLowerCase() !== "password") return;
|
|
526
|
+
state.typedAt = Date.now();
|
|
527
|
+
state.cached = read(target.form ?? doc);
|
|
528
|
+
}, true);
|
|
529
|
+
const Observer = globalThis.PerformanceObserver;
|
|
530
|
+
if (typeof Observer === "function") {
|
|
531
|
+
try {
|
|
532
|
+
new Observer((list) => {
|
|
533
|
+
if (!state.cached || Date.now() - state.typedAt > 1e4) return;
|
|
534
|
+
const fetched = list.getEntries().some((entry) => ["fetch", "xmlhttprequest"].includes(entry.initiatorType));
|
|
535
|
+
if (!fetched) return;
|
|
536
|
+
if (state.timer) clearTimeout(state.timer);
|
|
537
|
+
state.timer = setTimeout(() => {
|
|
538
|
+
const still = passwords(doc).some((input) => input.isConnected && input.value !== "" && input.getClientRects().length > 0);
|
|
539
|
+
if (!still) send2(state.cached);
|
|
540
|
+
}, 1500);
|
|
541
|
+
}).observe({ type: "resource", buffered: false });
|
|
542
|
+
} catch {
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
var LOGIN_SEEN_MESSAGE = "buddi-login-seen";
|
|
547
|
+
var LOGIN_PENDING_MESSAGE = "buddi-login-pending";
|
|
548
|
+
function heldLoginRequest(message, sender, heldTab) {
|
|
549
|
+
const request = message;
|
|
550
|
+
if (!request || typeof request.session !== "string" || request.session === "") return void 0;
|
|
551
|
+
if (heldTab === void 0 || sender?.tab?.id !== heldTab) return void 0;
|
|
552
|
+
const session = request.session;
|
|
553
|
+
const url = sender.url ?? "";
|
|
554
|
+
if (request.type === LOGIN_SEEN_MESSAGE) {
|
|
555
|
+
if (typeof request.password !== "string" || request.password === "" || request.password.length > 1024) return void 0;
|
|
556
|
+
const username = typeof request.username === "string" ? request.username.slice(0, 200) : "";
|
|
557
|
+
return { kind: "seen", session, tabId: heldTab, url, username, password: request.password, update: request.update === true };
|
|
558
|
+
}
|
|
559
|
+
if (request.type === LOGIN_PENDING_MESSAGE) return { kind: "pending", session, tabId: heldTab, url };
|
|
560
|
+
if (request.type === LOGIN_MESSAGE && (request.decision === "save" || request.decision === "later" || request.decision === "never")) {
|
|
561
|
+
return { kind: "answer", session, tabId: heldTab, decision: request.decision };
|
|
562
|
+
}
|
|
563
|
+
return void 0;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// src/held-logins.ts
|
|
567
|
+
var HELD_LOGIN_MS = 2 * 6e4;
|
|
568
|
+
var LOGIN_ACK_MS = 2e4;
|
|
569
|
+
var LOGIN_CHECK_MS = 5e3;
|
|
570
|
+
var NO_ANSWER = "buddi did not answer. Try again.";
|
|
571
|
+
var GONE = "That question is gone; add the login in Settings \u2192 Keys and secrets.";
|
|
572
|
+
function siteName(host) {
|
|
573
|
+
return host.toLowerCase().replace(/^www\./, "");
|
|
574
|
+
}
|
|
575
|
+
function siteKey(host) {
|
|
576
|
+
const labels = siteName(host).split(".").filter(Boolean);
|
|
577
|
+
if (labels.length <= 2) return labels.join(".");
|
|
578
|
+
const [second, top] = labels.slice(-2);
|
|
579
|
+
const keep = top.length === 2 && second.length <= 3 ? 3 : 2;
|
|
580
|
+
return labels.slice(-keep).join(".");
|
|
581
|
+
}
|
|
582
|
+
function webOrigin(url) {
|
|
583
|
+
try {
|
|
584
|
+
const parsed = new URL(url ?? "");
|
|
585
|
+
return parsed.protocol === "https:" || parsed.protocol === "http:" ? parsed : void 0;
|
|
586
|
+
} catch {
|
|
587
|
+
return void 0;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
var HeldLogins = class {
|
|
591
|
+
constructor(send2, options = {}) {
|
|
592
|
+
this.send = send2;
|
|
593
|
+
this.options = options;
|
|
594
|
+
}
|
|
595
|
+
#byTab = /* @__PURE__ */ new Map();
|
|
596
|
+
/** Frames waiting for buddi's answer, by id. */
|
|
597
|
+
#waiting = /* @__PURE__ */ new Map();
|
|
598
|
+
#id() {
|
|
599
|
+
return (this.options.uuid ?? (() => crypto.randomUUID()))();
|
|
600
|
+
}
|
|
601
|
+
/** Send a frame and wait for buddi's answer to it; undefined when none comes in time or the tab's pair goes. */
|
|
602
|
+
#ask(tabId, frame, ms) {
|
|
603
|
+
const id = this.#id();
|
|
604
|
+
return new Promise((resolve) => {
|
|
605
|
+
const timer2 = setTimeout(() => {
|
|
606
|
+
this.#waiting.delete(id);
|
|
607
|
+
resolve(void 0);
|
|
608
|
+
}, ms);
|
|
609
|
+
this.#waiting.set(id, { tabId, resolve, timer: timer2 });
|
|
610
|
+
this.send({ ...frame, id });
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* A sign-in went out in a held tab: keep the pair, with the origin Chrome
|
|
615
|
+
* reports for the page (never the page's claim), and ask buddi whether it
|
|
616
|
+
* is worth a question. Replaces whatever this tab was waiting on. Resolves
|
|
617
|
+
* to the question to draw, or undefined: a page not on the web, or nothing
|
|
618
|
+
* to ask (the pair is dropped then). With no word from buddi in time, the
|
|
619
|
+
* tab asks as the hold's facts said (`pair.update`).
|
|
620
|
+
*/
|
|
621
|
+
async capture(tabId, session, url, pair) {
|
|
622
|
+
const where = webOrigin(url);
|
|
623
|
+
if (!where || pair.password === "" || pair.password.length > 1024) return void 0;
|
|
624
|
+
this.clearTab(tabId);
|
|
625
|
+
let settle = () => {
|
|
626
|
+
};
|
|
627
|
+
const entry = {
|
|
628
|
+
tabId,
|
|
629
|
+
session,
|
|
630
|
+
origin: where.origin,
|
|
631
|
+
site: siteName(where.hostname),
|
|
632
|
+
username: pair.username.slice(0, 200),
|
|
633
|
+
password: pair.password,
|
|
634
|
+
update: pair.update === true,
|
|
635
|
+
timer: setTimeout(() => this.clearTab(tabId), this.options.holdMs ?? HELD_LOGIN_MS),
|
|
636
|
+
checked: new Promise((resolve) => {
|
|
637
|
+
settle = resolve;
|
|
638
|
+
})
|
|
639
|
+
};
|
|
640
|
+
this.#byTab.set(tabId, entry);
|
|
641
|
+
const word = await this.#ask(tabId, { type: "login", session, decision: "check", origin: entry.origin, username: entry.username, password: entry.password }, this.options.checkMs ?? LOGIN_CHECK_MS);
|
|
642
|
+
if (this.#byTab.get(tabId) !== entry) {
|
|
643
|
+
settle(false);
|
|
644
|
+
return void 0;
|
|
645
|
+
}
|
|
646
|
+
if (word && "ask" in word) {
|
|
647
|
+
if (word.ask === "none") {
|
|
648
|
+
this.clearTab(tabId);
|
|
649
|
+
settle(false);
|
|
650
|
+
return void 0;
|
|
651
|
+
}
|
|
652
|
+
entry.update = word.ask === "update";
|
|
653
|
+
}
|
|
654
|
+
settle(true);
|
|
655
|
+
return { site: entry.site, username: entry.username, update: entry.update };
|
|
656
|
+
}
|
|
657
|
+
/** The question still waiting in this tab, for a page of the same site (once buddi said it is worth asking). */
|
|
658
|
+
async pending(tabId, session, url) {
|
|
659
|
+
const entry = this.#byTab.get(tabId);
|
|
660
|
+
const where = webOrigin(url);
|
|
661
|
+
if (!entry || entry.session !== session || !where || siteKey(where.hostname) !== siteKey(entry.site)) return void 0;
|
|
662
|
+
if (!await entry.checked || this.#byTab.get(tabId) !== entry) return void 0;
|
|
663
|
+
return { site: entry.site, username: entry.username, update: entry.update };
|
|
664
|
+
}
|
|
665
|
+
/** Save: the pair goes to buddi with the original origin; the answer is what buddi says became of it. */
|
|
666
|
+
async save(tabId, session) {
|
|
667
|
+
const entry = this.#byTab.get(tabId);
|
|
668
|
+
if (!entry || entry.session !== session) return { saved: false, reason: GONE };
|
|
669
|
+
const answer = await this.#ask(tabId, { type: "login", session: entry.session, decision: "save", origin: entry.origin, username: entry.username, password: entry.password }, this.options.ackMs ?? LOGIN_ACK_MS);
|
|
670
|
+
if (!answer || !("saved" in answer)) return { saved: false, reason: NO_ANSWER };
|
|
671
|
+
if (answer.saved && this.#byTab.get(tabId) === entry) this.clearTab(tabId);
|
|
672
|
+
return answer;
|
|
673
|
+
}
|
|
674
|
+
/** Never for this site: the site alone goes to buddi, and the pair is dropped. */
|
|
675
|
+
never(tabId, session) {
|
|
676
|
+
const entry = this.#byTab.get(tabId);
|
|
677
|
+
if (!entry || entry.session !== session) return false;
|
|
678
|
+
this.clearTab(tabId);
|
|
679
|
+
this.send({ type: "login", session: entry.session, decision: "never", origin: entry.origin, username: entry.username });
|
|
680
|
+
return true;
|
|
681
|
+
}
|
|
682
|
+
/** Not now: nothing goes anywhere. */
|
|
683
|
+
dismiss(tabId, session) {
|
|
684
|
+
const entry = this.#byTab.get(tabId);
|
|
685
|
+
if (entry && entry.session === session) this.clearTab(tabId);
|
|
686
|
+
}
|
|
687
|
+
/** buddi's answer to a frame this worker sent. */
|
|
688
|
+
ack(id, answer) {
|
|
689
|
+
const waiting = this.#waiting.get(id);
|
|
690
|
+
if (!waiting) return;
|
|
691
|
+
this.#waiting.delete(id);
|
|
692
|
+
clearTimeout(waiting.timer);
|
|
693
|
+
waiting.resolve(answer);
|
|
694
|
+
}
|
|
695
|
+
clearTab(tabId) {
|
|
696
|
+
const entry = this.#byTab.get(tabId);
|
|
697
|
+
if (entry) {
|
|
698
|
+
this.#byTab.delete(tabId);
|
|
699
|
+
clearTimeout(entry.timer);
|
|
700
|
+
entry.password = "";
|
|
701
|
+
}
|
|
702
|
+
for (const [id, waiting] of this.#waiting) {
|
|
703
|
+
if (waiting.tabId !== tabId) continue;
|
|
704
|
+
this.#waiting.delete(id);
|
|
705
|
+
clearTimeout(waiting.timer);
|
|
706
|
+
waiting.resolve(void 0);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
clearSession(session) {
|
|
710
|
+
for (const entry of [...this.#byTab.values()]) if (entry.session === session) this.clearTab(entry.tabId);
|
|
711
|
+
}
|
|
712
|
+
clear() {
|
|
713
|
+
for (const tabId of [...this.#byTab.keys()]) this.clearTab(tabId);
|
|
714
|
+
}
|
|
715
|
+
get size() {
|
|
716
|
+
return this.#byTab.size;
|
|
717
|
+
}
|
|
718
|
+
};
|
|
303
719
|
|
|
304
720
|
// src/commands.ts
|
|
305
721
|
var TAKEN = "The owner took this tab; observe again to continue in a new one.";
|
|
@@ -307,6 +723,8 @@ var MOVED = "This page has changed since that observation. Observe again.";
|
|
|
307
723
|
var WATCHED = "You are looking at this tab. buddi waited and asked in the tab; nothing was done.";
|
|
308
724
|
var TAKEN_IN_TAB = "The owner took over this tab from the bar in it.";
|
|
309
725
|
var GROUP_TITLE = "buddi";
|
|
726
|
+
var NEW_WINDOW_NOTE = "Your Chrome couldn't open a tab in that window; opened a new window instead.";
|
|
727
|
+
var NO_WINDOW_NOTE = "Your Chrome had no ordinary window open, so the page opened in a new one, in the background.";
|
|
310
728
|
var MAX_FRAMES = 11;
|
|
311
729
|
var MAX_TREE = 32e3;
|
|
312
730
|
var MAX_TARGETS = 240;
|
|
@@ -503,6 +921,12 @@ var BrowserCommands = class {
|
|
|
503
921
|
#onEvent;
|
|
504
922
|
/** Sessions the owner let carry on in a tab they are looking at, by tab id. */
|
|
505
923
|
#inView = /* @__PURE__ */ new Map();
|
|
924
|
+
/** Sessions whose tab the owner holds (taken over in place), by tab id. */
|
|
925
|
+
#held = /* @__PURE__ */ new Map();
|
|
926
|
+
/** What a held tab need not ask about before offering to save a sign-in, as buddi said at the hold. */
|
|
927
|
+
#heldLogins = /* @__PURE__ */ new Map();
|
|
928
|
+
/** Sign-ins seen in held tabs, waiting for the owner's answer: one per tab, two minutes, gone on unhold or close. */
|
|
929
|
+
logins;
|
|
506
930
|
#now;
|
|
507
931
|
#wait;
|
|
508
932
|
/** How many things want the debugger on this tab. A screencast is one of them, and it outlives a command. */
|
|
@@ -512,6 +936,8 @@ var BrowserCommands = class {
|
|
|
512
936
|
#ownerWaitMs;
|
|
513
937
|
constructor(chrome2, options = {}) {
|
|
514
938
|
this.#chrome = chrome2;
|
|
939
|
+
this.logins = new HeldLogins(options.onLogin ?? (() => void 0));
|
|
940
|
+
chrome2.tabs.onRemoved?.addListener((tabId) => this.logins.clearTab(tabId));
|
|
515
941
|
this.#onEvent = options.onEvent ?? (() => void 0);
|
|
516
942
|
this.#ownerWaitMs = options.ownerWaitMs;
|
|
517
943
|
this.#wait = options.wait ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
@@ -525,6 +951,11 @@ var BrowserCommands = class {
|
|
|
525
951
|
const cast = this.#castsByTab.get(tabId);
|
|
526
952
|
if (cast) this.#forgetCast(cast);
|
|
527
953
|
});
|
|
954
|
+
chrome2.tabs.onUpdated?.addListener((tabId, change) => {
|
|
955
|
+
if (change.status !== "complete" || ![...this.#held.values()].includes(tabId)) return;
|
|
956
|
+
const session = [...this.#held].find(([, held]) => held === tabId)?.[0];
|
|
957
|
+
if (session) void this.#drawHeld(tabId, session);
|
|
958
|
+
});
|
|
528
959
|
this.#uuid = options.uuid ?? (() => crypto.randomUUID());
|
|
529
960
|
this.#contentFile = options.contentFile ?? "content.js";
|
|
530
961
|
}
|
|
@@ -561,6 +992,10 @@ var BrowserCommands = class {
|
|
|
561
992
|
}
|
|
562
993
|
case "input":
|
|
563
994
|
return this.#input(command, cancel);
|
|
995
|
+
case "hold":
|
|
996
|
+
return this.#hold(command, cancel);
|
|
997
|
+
case "unhold":
|
|
998
|
+
return this.#unhold(command.session);
|
|
564
999
|
case "close":
|
|
565
1000
|
return this.#close(command, cancel);
|
|
566
1001
|
default:
|
|
@@ -579,12 +1014,95 @@ var BrowserCommands = class {
|
|
|
579
1014
|
this.#sessions.clear();
|
|
580
1015
|
this.#refs.clear();
|
|
581
1016
|
this.#inView.clear();
|
|
1017
|
+
this.#held.clear();
|
|
1018
|
+
this.#heldLogins.clear();
|
|
1019
|
+
this.logins.clear();
|
|
1020
|
+
}
|
|
1021
|
+
/* ---- take-over in place ---- */
|
|
1022
|
+
/**
|
|
1023
|
+
* The owner takes the page where it is: the session's tab becomes the
|
|
1024
|
+
* active one, its window comes to the front, and the bar in it says buddi
|
|
1025
|
+
* is waiting, with Give it back. Nothing is streamed; it is their Chrome.
|
|
1026
|
+
*/
|
|
1027
|
+
async #hold(command, cancel) {
|
|
1028
|
+
const session = await this.#session(command.session);
|
|
1029
|
+
const tab = await this.#ownTab(session, this.#activeKey(session));
|
|
1030
|
+
const tabId = tab.id;
|
|
1031
|
+
cancel.check();
|
|
1032
|
+
cancel.dispatch();
|
|
1033
|
+
await this.#chrome.tabs.update(tabId, { active: true });
|
|
1034
|
+
if (tab.windowId !== void 0) await this.#chrome.windows.update?.(tab.windowId, { focused: true });
|
|
1035
|
+
this.#held.set(command.session, tabId);
|
|
1036
|
+
const logins = heldLoginFacts(command.args.logins);
|
|
1037
|
+
if (logins) this.#heldLogins.set(command.session, logins);
|
|
1038
|
+
else this.#heldLogins.delete(command.session);
|
|
1039
|
+
await this.#drawHeld(tabId, command.session);
|
|
1040
|
+
return {};
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* The waiting bar in a held tab, and the watch that offers to save a sign-in
|
|
1044
|
+
* the owner makes there (docs/browser.md, "Saving a sign-in"). Drawn again on
|
|
1045
|
+
* every page the tab loads while it is held.
|
|
1046
|
+
*/
|
|
1047
|
+
async #drawHeld(tabId, session) {
|
|
1048
|
+
await this.#chrome.scripting.executeScript({ target: { tabId }, func: showHeldBar, args: [session] }).catch(() => void 0);
|
|
1049
|
+
const facts = this.#heldLogins.get(session) ?? null;
|
|
1050
|
+
await this.#chrome.scripting.executeScript({ target: { tabId }, func: watchHeldLogins, args: [session, facts] }).catch(() => void 0);
|
|
1051
|
+
}
|
|
1052
|
+
/** The tab a session holds, when the owner holds one: where a save-prompt answer may come from. */
|
|
1053
|
+
heldTab(session) {
|
|
1054
|
+
return this.#held.get(session);
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* A sign-in message from a held tab's page (`heldLoginRequest`): a pair
|
|
1058
|
+
* seen, a page asking whether a question still waits for it, or the
|
|
1059
|
+
* owner's answer. The reply goes back to the page — a Save's is what buddi
|
|
1060
|
+
* said became of it. Undefined for a message this worker does not believe.
|
|
1061
|
+
*/
|
|
1062
|
+
loginMessage(message, sender) {
|
|
1063
|
+
const session = message?.session;
|
|
1064
|
+
const request = heldLoginRequest(message, sender, typeof session === "string" ? this.#held.get(session) : void 0);
|
|
1065
|
+
if (!request) return void 0;
|
|
1066
|
+
if (request.kind === "seen") return this.logins.capture(request.tabId, request.session, request.url, request).then((asked) => asked ?? null);
|
|
1067
|
+
if (request.kind === "pending") return this.logins.pending(request.tabId, request.session, request.url).then((asked) => asked ?? null);
|
|
1068
|
+
if (request.decision === "save") return this.logins.save(request.tabId, request.session);
|
|
1069
|
+
if (request.decision === "never") return Promise.resolve({ ok: this.logins.never(request.tabId, request.session) });
|
|
1070
|
+
this.logins.dismiss(request.tabId, request.session);
|
|
1071
|
+
return Promise.resolve({ ok: true });
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Given back (from the Canvas, or the bar): the bar goes, and the agent may
|
|
1075
|
+
* act in that tab although the owner is looking at it — they just handed it
|
|
1076
|
+
* over there, so waiting for them to leave it would be asking twice.
|
|
1077
|
+
*/
|
|
1078
|
+
async #unhold(session) {
|
|
1079
|
+
const tabId = this.#held.get(session);
|
|
1080
|
+
this.#held.delete(session);
|
|
1081
|
+
this.#heldLogins.delete(session);
|
|
1082
|
+
this.logins.clearSession(session);
|
|
1083
|
+
if (tabId === void 0) return {};
|
|
1084
|
+
this.#inView.set(session, tabId);
|
|
1085
|
+
await this.#chrome.scripting.executeScript({ target: { tabId }, func: hideHeldBar }).catch(() => void 0);
|
|
1086
|
+
return {};
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Give it back pressed in a held tab's bar. Refused from any other tab than
|
|
1090
|
+
* the one the session holds; a worker that was restarted since (and so
|
|
1091
|
+
* forgot what it held) passes it on, and the gateway resumes only a page
|
|
1092
|
+
* that is in the owner's hands. It hears it as the Canvas's Give it back.
|
|
1093
|
+
*/
|
|
1094
|
+
async giveBack(session, tabId) {
|
|
1095
|
+
const held = this.#held.get(session);
|
|
1096
|
+
if (tabId === void 0 || held !== void 0 && held !== tabId) return false;
|
|
1097
|
+
await this.#unhold(session);
|
|
1098
|
+
this.#onEvent({ type: "event", name: "giveback", session });
|
|
1099
|
+
return true;
|
|
582
1100
|
}
|
|
583
1101
|
/* ---- sessions and tabs ---- */
|
|
584
1102
|
async #session(session) {
|
|
585
1103
|
const existing = this.#sessions.get(session);
|
|
586
1104
|
if (existing) return existing;
|
|
587
|
-
const created = { groupId: -1, tabs: /* @__PURE__ */ new Map(), active: null, generation: 0 };
|
|
1105
|
+
const created = { groupId: -1, ungrouped: /* @__PURE__ */ new Set(), tabs: /* @__PURE__ */ new Map(), active: null, generation: 0 };
|
|
588
1106
|
this.#sessions.set(session, created);
|
|
589
1107
|
return created;
|
|
590
1108
|
}
|
|
@@ -608,12 +1126,17 @@ var BrowserCommands = class {
|
|
|
608
1126
|
this.#forget(session, key);
|
|
609
1127
|
throw new PreconditionError("That tab is gone. Observe again to continue in a new one.");
|
|
610
1128
|
}
|
|
611
|
-
if (session
|
|
1129
|
+
if (this.#taken(session, tab)) {
|
|
612
1130
|
this.#forget(session, key);
|
|
613
1131
|
throw new PreconditionError(TAKEN);
|
|
614
1132
|
}
|
|
615
1133
|
return tab;
|
|
616
1134
|
}
|
|
1135
|
+
/** The owner dragged this tab out of the session's group. A tab that never made it into the group cannot have been. */
|
|
1136
|
+
#taken(session, tab) {
|
|
1137
|
+
if (tab.id !== void 0 && session.ungrouped.has(tab.id)) return false;
|
|
1138
|
+
return session.groupId >= 0 && tab.groupId !== session.groupId;
|
|
1139
|
+
}
|
|
617
1140
|
#activeKey(session) {
|
|
618
1141
|
if (!session.active) throw new PreconditionError("This conversation has no browser tab yet. Navigate to open one.");
|
|
619
1142
|
return session.active;
|
|
@@ -671,19 +1194,82 @@ var BrowserCommands = class {
|
|
|
671
1194
|
#liveUrl(tab) {
|
|
672
1195
|
return checkUrl(tab.url ?? "");
|
|
673
1196
|
}
|
|
1197
|
+
/**
|
|
1198
|
+
* An ordinary window to open the tab in: the group's own when it has one,
|
|
1199
|
+
* else the one the owner used last, else any. `null` when Chrome has none
|
|
1200
|
+
* (only popups or installed apps' windows), `undefined` when this Chrome
|
|
1201
|
+
* cannot say, in which case Chrome picks as it always did.
|
|
1202
|
+
*/
|
|
1203
|
+
async #normalWindow(session) {
|
|
1204
|
+
const windows = this.#chrome.windows;
|
|
1205
|
+
if (!windows.getLastFocused && !windows.getAll) return void 0;
|
|
1206
|
+
const normal = (window) => window?.id !== void 0 && (window.type === void 0 || window.type === "normal");
|
|
1207
|
+
if (session.groupId >= 0) {
|
|
1208
|
+
const group = await this.#chrome.tabGroups.get(session.groupId).catch(() => void 0);
|
|
1209
|
+
if (group?.windowId !== void 0) {
|
|
1210
|
+
const found = await windows.get(group.windowId).catch(() => void 0);
|
|
1211
|
+
if (normal(found)) return found.id;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
const last2 = await windows.getLastFocused?.({ windowTypes: ["normal"] }).catch(() => void 0);
|
|
1215
|
+
if (normal(last2)) return last2.id;
|
|
1216
|
+
const all = await windows.getAll?.({ windowTypes: ["normal"] }).catch(() => void 0);
|
|
1217
|
+
return all?.find(normal)?.id ?? null;
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* One background tab, in an ordinary window, in the session's group.
|
|
1221
|
+
*
|
|
1222
|
+
* The group is cosmetic: Chrome refuses it in a window without a tab strip,
|
|
1223
|
+
* and that refusal must not undo a page that has already opened. No
|
|
1224
|
+
* ordinary window at all, or one that would not take the tab, and the page
|
|
1225
|
+
* opens in a new window that does not take the focus. Nothing here
|
|
1226
|
+
* activates a tab or focuses a window: the owner keeps working.
|
|
1227
|
+
*/
|
|
674
1228
|
async #openTab(session, url, cancel) {
|
|
1229
|
+
const windowId = await this.#normalWindow(session);
|
|
675
1230
|
cancel.dispatch();
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
if (
|
|
680
|
-
|
|
681
|
-
|
|
1231
|
+
let tab;
|
|
1232
|
+
let note;
|
|
1233
|
+
let groupable = true;
|
|
1234
|
+
if (windowId === void 0) {
|
|
1235
|
+
tab = await this.#chrome.tabs.create({ url, active: false });
|
|
1236
|
+
} else if (windowId !== null) {
|
|
1237
|
+
tab = await this.#chrome.tabs.create({ url, active: false, windowId }).catch(() => void 0);
|
|
1238
|
+
if (!tab) note = NEW_WINDOW_NOTE;
|
|
1239
|
+
} else {
|
|
1240
|
+
note = NO_WINDOW_NOTE;
|
|
1241
|
+
}
|
|
1242
|
+
if (!tab) {
|
|
1243
|
+
if (!this.#chrome.windows.create) throw new Error("Your Chrome has no ordinary window to open a tab in. Open a Chrome window and try again.");
|
|
1244
|
+
const created = await this.#chrome.windows.create({ url, focused: false, state: "normal" });
|
|
1245
|
+
tab = created.tabs?.[0];
|
|
1246
|
+
groupable = false;
|
|
1247
|
+
}
|
|
1248
|
+
if (tab?.id === void 0) throw new Error("Chrome opened a tab without an id.");
|
|
1249
|
+
const tabId = tab.id;
|
|
1250
|
+
if (groupable) {
|
|
1251
|
+
try {
|
|
1252
|
+
const groupId = await this.#chrome.tabs.group(session.groupId >= 0 ? { tabIds: [tabId], groupId: session.groupId } : { tabIds: [tabId] });
|
|
1253
|
+
if (session.groupId !== groupId) {
|
|
1254
|
+
session.groupId = groupId;
|
|
1255
|
+
await this.#chrome.tabGroups.update(groupId, { title: GROUP_TITLE, collapsed: false }).catch(() => void 0);
|
|
1256
|
+
}
|
|
1257
|
+
} catch {
|
|
1258
|
+
session.ungrouped.add(tabId);
|
|
1259
|
+
}
|
|
1260
|
+
} else {
|
|
1261
|
+
session.ungrouped.add(tabId);
|
|
682
1262
|
}
|
|
683
1263
|
const key = `tab-${this.#uuid().slice(0, 12)}`;
|
|
684
|
-
session.tabs.set(key,
|
|
1264
|
+
session.tabs.set(key, tabId);
|
|
685
1265
|
session.active = key;
|
|
686
|
-
return
|
|
1266
|
+
return { tabId, key, ...note ? { note } : {} };
|
|
1267
|
+
}
|
|
1268
|
+
/** What a failure after the tab opened carries back: the page is there. */
|
|
1269
|
+
async #openedError(error, tabId, key, url) {
|
|
1270
|
+
const tab = await this.#chrome.tabs.get(tabId).catch(() => void 0);
|
|
1271
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1272
|
+
return new OpenedError(message, { tabId: key, url: tab?.url ?? url, title: tab?.title ?? "" });
|
|
687
1273
|
}
|
|
688
1274
|
/** The key of this session's current tab, opening one when there is none to reuse. */
|
|
689
1275
|
async #reuseOrOpen(session, url, cancel) {
|
|
@@ -694,11 +1280,17 @@ var BrowserCommands = class {
|
|
|
694
1280
|
cancel.dispatch();
|
|
695
1281
|
await this.#chrome.tabs.update(tab.id, { url });
|
|
696
1282
|
await this.#waitForLoad(tab.id);
|
|
697
|
-
return;
|
|
1283
|
+
return void 0;
|
|
698
1284
|
}
|
|
699
1285
|
}
|
|
700
1286
|
const opened = await this.#openTab(session, url, cancel);
|
|
701
|
-
|
|
1287
|
+
try {
|
|
1288
|
+
await this.#waitForLoad(opened.tabId);
|
|
1289
|
+
} catch (error) {
|
|
1290
|
+
if (error instanceof CancelledError) throw error;
|
|
1291
|
+
throw await this.#openedError(error, opened.tabId, opened.key, url);
|
|
1292
|
+
}
|
|
1293
|
+
return opened.note;
|
|
702
1294
|
}
|
|
703
1295
|
/**
|
|
704
1296
|
* Wait for what the input may have started, not for what was already there.
|
|
@@ -738,8 +1330,8 @@ var BrowserCommands = class {
|
|
|
738
1330
|
session.observed = void 0;
|
|
739
1331
|
this.#refs.delete(command.session);
|
|
740
1332
|
cancel.check();
|
|
741
|
-
await this.#reuseOrOpen(session, url, cancel);
|
|
742
|
-
return {};
|
|
1333
|
+
const note = await this.#reuseOrOpen(session, url, cancel);
|
|
1334
|
+
return note ? { observation: { note } } : {};
|
|
743
1335
|
}
|
|
744
1336
|
async #tab(command) {
|
|
745
1337
|
const session = await this.#session(command.session);
|
|
@@ -760,7 +1352,7 @@ var BrowserCommands = class {
|
|
|
760
1352
|
for (const [key, tabId] of [...session.tabs]) {
|
|
761
1353
|
const tab = await this.#chrome.tabs.get(tabId).catch(() => void 0);
|
|
762
1354
|
if (!tab) continue;
|
|
763
|
-
if (session
|
|
1355
|
+
if (this.#taken(session, tab)) {
|
|
764
1356
|
this.#forget(session, key);
|
|
765
1357
|
continue;
|
|
766
1358
|
}
|
|
@@ -780,7 +1372,7 @@ var BrowserCommands = class {
|
|
|
780
1372
|
const live = [];
|
|
781
1373
|
for (const [key, tabId] of [...session.tabs]) {
|
|
782
1374
|
const tab = await this.#chrome.tabs.get(tabId).catch(() => void 0);
|
|
783
|
-
if (!tab || session
|
|
1375
|
+
if (!tab || this.#taken(session, tab)) {
|
|
784
1376
|
this.#forget(session, key);
|
|
785
1377
|
continue;
|
|
786
1378
|
}
|
|
@@ -1120,7 +1712,7 @@ Scroll: ${scroll.x}, ${scroll.y}`.slice(0, MAX_TREE);
|
|
|
1120
1712
|
await this.#stopScreencast(command.session);
|
|
1121
1713
|
throw new PreconditionError("That tab is gone. Observe again to continue in a new one.");
|
|
1122
1714
|
}
|
|
1123
|
-
if (session
|
|
1715
|
+
if (this.#taken(session, tab)) {
|
|
1124
1716
|
await this.#stopScreencast(command.session);
|
|
1125
1717
|
throw new PreconditionError(TAKEN);
|
|
1126
1718
|
}
|
|
@@ -1353,6 +1945,13 @@ function pressRef(ref, key, code, keyCode) {
|
|
|
1353
1945
|
element.dispatchEvent(new KeyboardEvent(type, { key, code, keyCode, bubbles: true, cancelable: true }));
|
|
1354
1946
|
}
|
|
1355
1947
|
}
|
|
1948
|
+
function heldLoginFacts(raw) {
|
|
1949
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
1950
|
+
const { never, saved } = raw;
|
|
1951
|
+
const sites = Array.isArray(never) ? never.filter((site) => typeof site === "string" && site.length <= 253).slice(0, 500) : [];
|
|
1952
|
+
const kept = Array.isArray(saved) ? saved.filter((login) => typeof login?.site === "string" && typeof login?.username === "string").slice(0, 500).map(({ site, username }) => ({ site, username })) : [];
|
|
1953
|
+
return { never: sites, saved: kept };
|
|
1954
|
+
}
|
|
1356
1955
|
|
|
1357
1956
|
// src/external.ts
|
|
1358
1957
|
function isLoopbackOrigin(origin) {
|
|
@@ -1428,7 +2027,7 @@ function flush(open) {
|
|
|
1428
2027
|
}
|
|
1429
2028
|
}
|
|
1430
2029
|
}
|
|
1431
|
-
var commands = new BrowserCommands(chrome, { onFrame: send, onEvent: send });
|
|
2030
|
+
var commands = new BrowserCommands(chrome, { onFrame: send, onEvent: send, onLogin: send });
|
|
1432
2031
|
var attempt = 0;
|
|
1433
2032
|
var timer;
|
|
1434
2033
|
var silence;
|
|
@@ -1441,6 +2040,7 @@ var protocol = new Protocol({
|
|
|
1441
2040
|
execute: (command, cancel) => commands.run(command, cancel),
|
|
1442
2041
|
// The socket ended: sessions and refs go with it. The tabs do not.
|
|
1443
2042
|
onReset: () => commands.reset(),
|
|
2043
|
+
onLoginAck: (id, answer) => commands.logins.ack(id, answer),
|
|
1444
2044
|
onState: (state) => {
|
|
1445
2045
|
last = state;
|
|
1446
2046
|
safely(() => chrome.runtime.sendMessage({ type: "buddi-state", state }).catch(() => void 0));
|
|
@@ -1534,9 +2134,23 @@ function schedule() {
|
|
|
1534
2134
|
safely(() => connect());
|
|
1535
2135
|
}, wait);
|
|
1536
2136
|
}
|
|
1537
|
-
chrome.runtime.onMessage.addListener((message,
|
|
2137
|
+
chrome.runtime.onMessage.addListener((message, sender, respond) => {
|
|
1538
2138
|
const request = message;
|
|
1539
2139
|
if (!request || typeof request.type !== "string") return;
|
|
2140
|
+
if (request.type === GIVE_BACK_MESSAGE) {
|
|
2141
|
+
const tabId = sender?.tab?.id;
|
|
2142
|
+
if (typeof request.session === "string") safely(() => commands.giveBack(request.session, tabId).then(() => void 0));
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2145
|
+
if (request.type === LOGIN_MESSAGE || request.type === LOGIN_SEEN_MESSAGE || request.type === LOGIN_PENDING_MESSAGE) {
|
|
2146
|
+
const reply = commands.loginMessage(message, sender);
|
|
2147
|
+
if (!reply) {
|
|
2148
|
+
respond(null);
|
|
2149
|
+
return;
|
|
2150
|
+
}
|
|
2151
|
+
safely(() => reply.then((answer) => respond(answer), () => respond(null)));
|
|
2152
|
+
return true;
|
|
2153
|
+
}
|
|
1540
2154
|
if (request.type === "buddi-get-state") {
|
|
1541
2155
|
respond({ state: last });
|
|
1542
2156
|
return;
|