@aryanduntley/pwa-debug 0.1.4 → 0.1.6

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.
Files changed (2) hide show
  1. package/dist/main.js +71 -15
  2. package/package.json +3 -3
package/dist/main.js CHANGED
@@ -12818,13 +12818,20 @@ const freshFlags = (port, userDataDir) => Object.freeze([
12818
12818
  * --load-extension, and --disable-extensions-except would additionally block
12819
12819
  * the manual Load-unpack the user is steered to. The profile/port still come
12820
12820
  * up; the extension is provisioned by hand afterward.
12821
+ *
12822
+ * `isolate` (default true) controls --disable-extensions-except, which pins the
12823
+ * profile to ONLY pwa-debug — Chromium disables every other extension, including
12824
+ * ones already in the persistent profile or Load-unpacked/installed after launch.
12825
+ * Pass false to drop it so other extensions coexist: --load-extension still
12826
+ * preloads pwa-debug, while the profile's other extensions stay enabled. No-op
12827
+ * under 'manual-guided' (the flag is already omitted there).
12821
12828
  */
12822
- const sandboxFlags = (port, userDataDir, extensionPath, strategy) => {
12829
+ const sandboxFlags = (port, userDataDir, extensionPath, strategy, isolate) => {
12823
12830
  const extensionFlags = strategy === 'manual-guided'
12824
12831
  ? []
12825
12832
  : [
12826
12833
  `--load-extension=${extensionPath}`,
12827
- `--disable-extensions-except=${extensionPath}`,
12834
+ ...(isolate ? [`--disable-extensions-except=${extensionPath}`] : []),
12828
12835
  ...(strategy === 'load-flag-escape-hatch'
12829
12836
  ? ['--disable-features=DisableLoadExtensionCommandLineSwitch']
12830
12837
  : []),
@@ -12872,7 +12879,8 @@ const buildNewWindowFlatpakArgs = (appId) => flatpakRun(appId, Object.freeze(['-
12872
12879
  /**
12873
12880
  * Sandbox launch: dedicated profile + the pwa-debug extension preloaded BEFORE
12874
12881
  * any tab opens (so the content-script injection race cannot occur).
12875
- * --disable-extensions-except pins the profile to only our extension.
12882
+ * --disable-extensions-except pins the profile to only our extension when
12883
+ * `isolate` is true (the default); pass false to let other extensions coexist.
12876
12884
  *
12877
12885
  * --disable-session-crashed-bubble + --hide-crash-restore-bubble suppress the
12878
12886
  * "Brave/Chrome didn't shut down correctly — restore tabs?" prompt on the NEXT
@@ -12883,9 +12891,9 @@ const buildNewWindowFlatpakArgs = (appId) => flatpakRun(appId, Object.freeze(['-
12883
12891
  * Applied to sandbox modes only — an 'existing'-mode launch is the user's real
12884
12892
  * profile, where a genuine restore prompt should be left intact.
12885
12893
  */
12886
- const buildSandboxSpawnArgs = (execPath, port, userDataDir, extensionPath, strategy) => Object.freeze({
12894
+ const buildSandboxSpawnArgs = (execPath, port, userDataDir, extensionPath, strategy, isolate = true) => Object.freeze({
12887
12895
  cmd: execPath,
12888
- args: sandboxFlags(port, userDataDir, extensionPath, strategy),
12896
+ args: sandboxFlags(port, userDataDir, extensionPath, strategy, isolate),
12889
12897
  });
12890
12898
  /**
12891
12899
  * Sandbox launch for a flatpak browser: `flatpak run <app-id> <sandbox flags>`.
@@ -12894,7 +12902,7 @@ const buildSandboxSpawnArgs = (execPath, port, userDataDir, extensionPath, strat
12894
12902
  * (`flatpak override --user --filesystem=host <app-id>`) for these to resolve
12895
12903
  * inside the sandbox — the same prerequisite the NMH path documents.
12896
12904
  */
12897
- const buildSandboxFlatpakArgs = (appId, port, userDataDir, extensionPath, strategy) => flatpakRun(appId, sandboxFlags(port, userDataDir, extensionPath, strategy));
12905
+ const buildSandboxFlatpakArgs = (appId, port, userDataDir, extensionPath, strategy, isolate = true) => flatpakRun(appId, sandboxFlags(port, userDataDir, extensionPath, strategy, isolate));
12898
12906
 
12899
12907
  /**
12900
12908
  * 'existing'-mode launch: the graceful-degradation triad orchestrated over the
@@ -13002,9 +13010,11 @@ const launchSandbox = async (input, deps) => {
13002
13010
  // refuse the unpacked --load-extension (#318). Seed developer_mode=true into the
13003
13011
  // profile's Preferences before spawn so the extension loads with no manual step.
13004
13012
  await deps.seedDeveloperMode(input.userDataDir);
13013
+ // Default to isolation (clean-room) when unset; false lets other extensions coexist.
13014
+ const isolate = input.isolateExtensions ?? true;
13005
13015
  const { cmd, args } = input.appId
13006
- ? buildSandboxFlatpakArgs(input.appId, input.port, input.userDataDir, input.extensionPath, input.loadStrategy)
13007
- : buildSandboxSpawnArgs(input.execPath, input.port, input.userDataDir, input.extensionPath, input.loadStrategy);
13016
+ ? buildSandboxFlatpakArgs(input.appId, input.port, input.userDataDir, input.extensionPath, input.loadStrategy, isolate)
13017
+ : buildSandboxSpawnArgs(input.execPath, input.port, input.userDataDir, input.extensionPath, input.loadStrategy, isolate);
13008
13018
  const { pid } = await deps.spawnBrowser(cmd, args);
13009
13019
  if (input.mode === 'sandbox-temp') {
13010
13020
  deps.registerTempProfile(input.userDataDir);
@@ -14009,6 +14019,7 @@ const inputSchema$5 = {
14009
14019
  port: numberType().int().min(1).max(65535).optional(),
14010
14020
  mode: enumType(MODES).optional(),
14011
14021
  packaging: enumType(PACKAGINGS).optional(),
14022
+ isolateExtensions: booleanType().optional(),
14012
14023
  };
14013
14024
  const isSandboxMode = (mode) => mode === 'sandbox-persistent' || mode === 'sandbox-temp';
14014
14025
  /**
@@ -14200,6 +14211,9 @@ const launchBrowserCore = async (args, platform, env, deps) => {
14200
14211
  loadStrategy,
14201
14212
  mode,
14202
14213
  refreshExtension: refreshExtensionEnabled(mode, env),
14214
+ ...(args.isolateExtensions !== undefined
14215
+ ? { isolateExtensions: args.isolateExtensions }
14216
+ : {}),
14203
14217
  ...(target.appId !== undefined ? { appId: target.appId } : {}),
14204
14218
  ...(snapPkg ? { snapPackage: snapPkg } : {}),
14205
14219
  });
@@ -14250,7 +14264,7 @@ const launchBrowserHandler = async (args, ctx) => launchBrowserCore(args, proces
14250
14264
  });
14251
14265
  const launchBrowserTool = Object.freeze({
14252
14266
  name: 'pdl_launch_browser',
14253
- description: "Launch or attach to a Chromium-family browser with a live remote-debugging port, for use alongside chrome-devtools-mcp. Modes: mode='existing' (default) targets the user's normal profile and degrades gracefully — (a) port already live → attach; (b) running without a debug port → opens a NEW WINDOW in the existing session (never kills it), attached:false + degradation message; (c) not running → spawns fresh with --remote-debugging-port + --user-data-dir=<your profile>. mode='sandbox-persistent' spawns a dedicated, persistent dev profile at ~/.pwa-debug/profiles/<browser>/ beside your normal browser, with the pwa-debug extension PRELOADED (no reload needed); mode='sandbox-temp' is the same but in a throwaway mkdtemp profile cleaned up on host shutdown. Sandbox modes always work standalone (separate profile → no lock collision) and both pwa-debug + CDP tools are available. Args: browser? (chrome|chromium|edge|brave|vivaldi|opera; defaults to system-default), port? (default 9222), mode?, packaging? (native|snap|flatpak). When the same browser is installed under multiple packagings (e.g. snap AND flatpak chromium), pass packaging to pick one; without it the default preference is native > snap > flatpak and next_steps lists the alternatives so you can re-target. Linux is first-class; macOS/Windows deferred. Follow next_steps[] — it carries the chrome-devtools-mcp registration snippet, the profile location, the flatpak onboarding steps, or the degradation guidance.",
14267
+ description: "Launch or attach to a Chromium-family browser with a live remote-debugging port, for use alongside chrome-devtools-mcp. Modes: mode='existing' (default) targets the user's normal profile and degrades gracefully — (a) port already live → attach; (b) running without a debug port → opens a NEW WINDOW in the existing session (never kills it), attached:false + degradation message; (c) not running → spawns fresh with --remote-debugging-port + --user-data-dir=<your profile>. mode='sandbox-persistent' spawns a dedicated, persistent dev profile at ~/.pwa-debug/profiles/<browser>/ beside your normal browser, with the pwa-debug extension PRELOADED (no reload needed); mode='sandbox-temp' is the same but in a throwaway mkdtemp profile cleaned up on host shutdown. Sandbox modes always work standalone (separate profile → no lock collision) and both pwa-debug + CDP tools are available. Args: browser? (chrome|chromium|edge|brave|vivaldi|opera; defaults to system-default), port? (default 9222), mode?, packaging? (native|snap|flatpak). When the same browser is installed under multiple packagings (e.g. snap AND flatpak chromium), pass packaging to pick one; without it the default preference is native > snap > flatpak and next_steps lists the alternatives so you can re-target. isolateExtensions? (sandbox modes only, default true): true pins the dedicated profile to ONLY the pwa-debug extension (clean room — every other extension is disabled); pass false to let other extensions coexist (pwa-debug still preloads, while extensions already in the persistent profile or Load-unpacked/installed after launch stay enabled) — use this to debug a PWA alongside other extensions or to test your own extension with pwa-debug. existing mode already keeps all your normal-profile extensions. Linux is first-class; macOS/Windows deferred. Follow next_steps[] — it carries the chrome-devtools-mcp registration snippet, the profile location, the flatpak onboarding steps, or the degradation guidance.",
14254
14268
  inputSchema: inputSchema$5,
14255
14269
  handler: launchBrowserHandler,
14256
14270
  });
@@ -15027,6 +15041,21 @@ const snapshotConn = (c) => Object.freeze({
15027
15041
  connectedAt: c.connectedAt,
15028
15042
  lastSeenAt: c.lastSeenAt,
15029
15043
  });
15044
+ // Probe whether a unix socket path has a live listener. Resolves 'live' if a
15045
+ // connection is accepted, 'stale' if the path refuses connection (ECONNREFUSED)
15046
+ // or is gone (ENOENT) — i.e. an orphaned socket file left behind by a host that
15047
+ // was hard-killed (SIGKILL / terminal close / crash) before close() could
15048
+ // unlink it. Used to decide whether an EADDRINUSE bind failure is a genuine
15049
+ // conflict (another host is up) or a reclaimable stale file.
15050
+ const probeSocketLiveness = (path) => new Promise((resolve) => {
15051
+ const probe = createConnection(path);
15052
+ const settle = (result) => {
15053
+ probe.destroy();
15054
+ resolve(result);
15055
+ };
15056
+ probe.once('connect', () => settle('live'));
15057
+ probe.once('error', () => settle('stale'));
15058
+ });
15030
15059
  const createIpcServer = async (opts) => {
15031
15060
  const connections = new Map();
15032
15061
  const pending = new Map();
@@ -15117,12 +15146,39 @@ const createIpcServer = async (opts) => {
15117
15146
  const socketPaths = [opts.socketPath, ...(opts.extraSocketPaths ?? [])];
15118
15147
  const servers = socketPaths.map(() => createServer(handleSocket));
15119
15148
  const listenOne = (server, path) => new Promise((resolve, reject) => {
15120
- const onError = (err) => reject(err);
15121
- server.once('error', onError);
15122
- server.listen(path, () => {
15123
- server.off('error', onError);
15124
- resolve();
15125
- });
15149
+ const attempt = (recovered) => {
15150
+ const onError = (err) => {
15151
+ const recoverable = err.code === 'EADDRINUSE' &&
15152
+ !recovered &&
15153
+ process.platform !== 'win32';
15154
+ if (!recoverable) {
15155
+ reject(err);
15156
+ return;
15157
+ }
15158
+ // A prior host may have been hard-killed without unlinking its socket.
15159
+ // Probe before clobbering: only reclaim a path nothing is listening on
15160
+ // — never unlink out from under a host that is genuinely up.
15161
+ void probeSocketLiveness(path).then(async (liveness) => {
15162
+ if (liveness === 'live') {
15163
+ reject(new Error(`ipc server: another pwa-debug host is already listening on ${path}`));
15164
+ return;
15165
+ }
15166
+ try {
15167
+ await unlink(path);
15168
+ }
15169
+ catch {
15170
+ // already gone — fine, just retry the bind
15171
+ }
15172
+ attempt(true);
15173
+ });
15174
+ };
15175
+ server.once('error', onError);
15176
+ server.listen(path, () => {
15177
+ server.off('error', onError);
15178
+ resolve();
15179
+ });
15180
+ };
15181
+ attempt(false);
15126
15182
  });
15127
15183
  await Promise.all(servers.map((s, i) => listenOne(s, socketPaths[i])));
15128
15184
  const sendTo = (extensionId, env) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aryanduntley/pwa-debug",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -63,8 +63,8 @@
63
63
  "@types/winreg": "^1.2.36",
64
64
  "rollup": "^4.27.4",
65
65
  "tslib": "^2.8.1",
66
- "@pwa-debug/extension": "0.1.4",
67
- "@pwa-debug/shared": "0.1.4"
66
+ "@pwa-debug/extension": "0.1.6",
67
+ "@pwa-debug/shared": "0.1.6"
68
68
  },
69
69
  "scripts": {
70
70
  "typecheck": "tsc --noEmit",