@bridgekitux/browser-bridge 0.1.0 → 0.2.0

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.
@@ -24,7 +24,7 @@ async function ensureSocket() {
24
24
  return;
25
25
  }
26
26
  if (message.type === 'event') {
27
- for (const port of ports) port.postMessage({ source: 'bridgekit-extension', type: 'bridgekit.event', message });
27
+ for (const port of ports) safePostToPort(port, { source: 'bridgekit-extension', type: 'bridgekit.event', message });
28
28
  return;
29
29
  }
30
30
  const owner = message.id ? requestOwners.get(message.id) : undefined;
@@ -33,12 +33,12 @@ async function ensureSocket() {
33
33
  pending.delete(message.id);
34
34
  requestOwners.delete(message.id);
35
35
  }
36
- if (owner && ports.has(owner)) owner.postMessage({ source: 'bridgekit-extension', type: 'bridgekit.response', message });
36
+ if (owner && ports.has(owner)) safePostToPort(owner, { source: 'bridgekit-extension', type: 'bridgekit.response', message });
37
37
  else if (callback) callback(message);
38
38
  });
39
39
  socket.addEventListener('close', () => {
40
40
  for (const [id, owner] of requestOwners.entries()) {
41
- if (ports.has(owner)) owner.postMessage({ source: 'bridgekit-extension', type: 'bridgekit.response', message: bridgeError('bridge_closed', 'BridgeKit agent connection closed', id) });
41
+ if (ports.has(owner)) safePostToPort(owner, { source: 'bridgekit-extension', type: 'bridgekit.response', message: bridgeError('bridge_closed', 'BridgeKit agent connection closed', id) });
42
42
  }
43
43
  requestOwners.clear();
44
44
  pending.clear();
@@ -52,30 +52,48 @@ async function isSenderAllowed(sender, pageOrigin) {
52
52
  return Boolean(pageOrigin && senderOrigin === pageOrigin && isOriginAllowed(pageOrigin, policy.allowedOrigins));
53
53
  }
54
54
 
55
+ function forgetPort(port) {
56
+ ports.delete(port);
57
+ for (const [id, owner] of requestOwners.entries()) {
58
+ if (owner === port) requestOwners.delete(id);
59
+ }
60
+ }
61
+
62
+ function safePostToPort(port, message) {
63
+ if (!ports.has(port)) return false;
64
+ try {
65
+ port.postMessage(message);
66
+ return true;
67
+ } catch {
68
+ forgetPort(port);
69
+ return false;
70
+ }
71
+ }
72
+
55
73
  chrome.runtime.onConnect.addListener((port) => {
56
74
  if (port.name !== 'bridgekit-page') return;
57
75
  ports.add(port);
58
76
  port.onDisconnect.addListener(() => {
59
- ports.delete(port);
60
- for (const [id, owner] of requestOwners.entries()) {
61
- if (owner === port) requestOwners.delete(id);
62
- }
77
+ // Reading runtime.lastError clears benign Chrome warnings caused by BFCache
78
+ // or navigation closing a long-lived extension port.
79
+ void chrome.runtime.lastError;
80
+ forgetPort(port);
63
81
  });
64
82
  port.onMessage.addListener((request) => {
65
83
  void (async () => {
66
84
  const pageOrigin = typeof request?.pageOrigin === 'string' ? request.pageOrigin : undefined;
67
85
  if (!request || request.source !== 'bridgekit-client') return;
68
86
  if (!(await isSenderAllowed(port.sender, pageOrigin))) {
69
- port.postMessage({ source: 'bridgekit-extension', type: 'bridgekit.response', message: bridgeError('origin_not_allowed', `BridgeKit origin is not allowed: ${pageOrigin ?? 'unknown'}`, request.message?.id) });
87
+ safePostToPort(port, { source: 'bridgekit-extension', type: 'bridgekit.response', message: bridgeError('origin_not_allowed', `BridgeKit origin is not allowed: ${pageOrigin ?? 'unknown'}`, request.message?.id) });
70
88
  return;
71
89
  }
72
90
  if (request.type === 'bridgekit.detect') {
73
91
  const policy = await getPolicy();
74
- port.postMessage({ source: 'bridgekit-extension', type: 'bridgekit.available', agentUrl: policy.agentUrl, allowed: true });
92
+ safePostToPort(port, { source: 'bridgekit-extension', type: 'bridgekit.available', agentUrl: policy.agentUrl, allowed: true });
75
93
  return;
76
94
  }
77
95
  if (request.type !== 'bridgekit.request' || !validBridgeMessage(request.message)) {
78
- port.postMessage({ source: 'bridgekit-extension', type: 'bridgekit.response', message: bridgeError('invalid_bridge_message', 'Invalid BridgeKit message', request.message?.id) });
96
+ safePostToPort(port, { source: 'bridgekit-extension', type: 'bridgekit.response', message: bridgeError('invalid_bridge_message', 'Invalid BridgeKit message', request.message?.id) });
79
97
  return;
80
98
  }
81
99
  const ws = await ensureSocket();
@@ -86,7 +104,7 @@ chrome.runtime.onConnect.addListener((port) => {
86
104
  if (ws.readyState === WebSocket.OPEN) send();
87
105
  else ws.addEventListener('open', send, { once: true });
88
106
  ws.addEventListener('error', () => {
89
- port.postMessage({ source: 'bridgekit-extension', type: 'bridgekit.response', message: bridgeError('agent_unavailable', 'BridgeKit local agent is not reachable', request.message.id) });
107
+ safePostToPort(port, { source: 'bridgekit-extension', type: 'bridgekit.response', message: bridgeError('agent_unavailable', 'BridgeKit local agent is not reachable', request.message.id) });
90
108
  }, { once: true });
91
109
  })();
92
110
  });
@@ -1,7 +1,41 @@
1
- const port = chrome.runtime.connect({ name: 'bridgekit-page' });
1
+ let port;
2
+ let reconnectTimer;
2
3
  const pageOrigin = window.location.origin;
4
+ const pendingMessages = [];
5
+ let pageIsUnloading = false;
3
6
 
4
- port.onMessage.addListener((message) => {
7
+ function connectPort() {
8
+ if (port) return port;
9
+ try {
10
+ port = chrome.runtime.connect({ name: 'bridgekit-page' });
11
+ } catch {
12
+ scheduleReconnect();
13
+ return undefined;
14
+ }
15
+
16
+ port.onMessage.addListener(handleExtensionMessage);
17
+ port.onDisconnect.addListener(() => {
18
+ // Read runtime.lastError to clear benign Chrome warnings when BFCache,
19
+ // reloads, or navigation close the long-lived message channel.
20
+ void chrome.runtime.lastError;
21
+ port = undefined;
22
+ if (!pageIsUnloading) scheduleReconnect();
23
+ });
24
+
25
+ flushPendingMessages();
26
+ postToExtension({ source: 'bridgekit-client', type: 'bridgekit.detect' });
27
+ return port;
28
+ }
29
+
30
+ function scheduleReconnect() {
31
+ if (reconnectTimer || pageIsUnloading) return;
32
+ reconnectTimer = setTimeout(() => {
33
+ reconnectTimer = undefined;
34
+ connectPort();
35
+ }, 250);
36
+ }
37
+
38
+ function handleExtensionMessage(message) {
5
39
  if (!message || message.source !== 'bridgekit-extension') return;
6
40
  if (message.type === 'bridgekit.event') {
7
41
  window.postMessage({ source: 'bridgekit-bridge', type: 'bridgekit.event', message: message.message }, pageOrigin);
@@ -12,13 +46,61 @@ port.onMessage.addListener((message) => {
12
46
  return;
13
47
  }
14
48
  window.postMessage({ source: 'bridgekit-bridge', type: 'bridgekit.response', message: message.message }, pageOrigin);
15
- });
49
+ }
50
+
51
+ function postToExtension(data) {
52
+ const message = { ...data, pageOrigin };
53
+ const activePort = port ?? connectPort();
54
+ if (!activePort) {
55
+ pendingMessages.push(message);
56
+ return;
57
+ }
58
+ try {
59
+ activePort.postMessage(message);
60
+ } catch {
61
+ port = undefined;
62
+ pendingMessages.push(message);
63
+ scheduleReconnect();
64
+ }
65
+ }
66
+
67
+ function flushPendingMessages() {
68
+ if (!port) return;
69
+ while (pendingMessages.length > 0) {
70
+ const message = pendingMessages.shift();
71
+ try {
72
+ port.postMessage(message);
73
+ } catch {
74
+ port = undefined;
75
+ if (message) pendingMessages.unshift(message);
76
+ scheduleReconnect();
77
+ return;
78
+ }
79
+ }
80
+ }
16
81
 
17
82
  window.addEventListener('message', (event) => {
18
83
  if (event.source !== window || event.origin !== pageOrigin) return;
19
84
  const data = event.data;
20
85
  if (!data || data.source !== 'bridgekit-client') return;
21
- port.postMessage({ ...data, pageOrigin });
86
+ postToExtension(data);
87
+ });
88
+
89
+ window.addEventListener('pagehide', () => {
90
+ pageIsUnloading = true;
91
+ if (reconnectTimer) clearTimeout(reconnectTimer);
92
+ reconnectTimer = undefined;
93
+ try {
94
+ port?.disconnect();
95
+ } catch {
96
+ // Port may already be closed by navigation or BFCache.
97
+ }
98
+ port = undefined;
99
+ });
100
+
101
+ window.addEventListener('pageshow', () => {
102
+ pageIsUnloading = false;
103
+ connectPort();
22
104
  });
23
105
 
24
- port.postMessage({ source: 'bridgekit-client', type: 'bridgekit.detect', pageOrigin });
106
+ connectPort();
@@ -1,10 +1,15 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "BridgeKit Browser Bridge",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
5
5
  "description": "Relays approved BridgeKit requests from WebContainers and browser sandboxes to the local BridgeKit agent.",
6
- "permissions": ["storage"],
7
- "host_permissions": ["http://127.0.0.1/*", "ws://127.0.0.1/*"],
6
+ "permissions": [
7
+ "storage"
8
+ ],
9
+ "host_permissions": [
10
+ "http://127.0.0.1/*",
11
+ "ws://127.0.0.1/*"
12
+ ],
8
13
  "background": {
9
14
  "service_worker": "background.js",
10
15
  "type": "module"
@@ -20,10 +25,14 @@
20
25
  "https://*.bolt.new/*",
21
26
  "https://stackblitz.com/*",
22
27
  "https://*.stackblitz.com/*",
28
+ "https://*.webcontainer.io/*",
29
+ "https://*.local-credentialless.webcontainer.io/*",
23
30
  "http://localhost/*",
24
31
  "http://127.0.0.1/*"
25
32
  ],
26
- "js": ["content-script.js"],
33
+ "js": [
34
+ "content-script.js"
35
+ ],
27
36
  "run_at": "document_start"
28
37
  }
29
38
  ]
@@ -4,6 +4,8 @@ export const DEFAULT_ALLOWED_ORIGINS = [
4
4
  'https://*.bolt.new',
5
5
  'https://stackblitz.com',
6
6
  'https://*.stackblitz.com',
7
+ 'https://*.webcontainer.io',
8
+ 'https://*.local-credentialless.webcontainer.io',
7
9
  'http://localhost:*',
8
10
  'http://127.0.0.1:*'
9
11
  ];
@@ -26,17 +28,22 @@ export function isOriginAllowed(origin, patterns) {
26
28
  return patterns.some((pattern) => originMatchesPattern(parsed, pattern));
27
29
  }
28
30
 
31
+ function normalizeOriginPattern(pattern) {
32
+ return pattern.trim().replace(/\/\*$/, '').replace(/\/$/, '');
33
+ }
34
+
29
35
  function originMatchesPattern(origin, pattern) {
36
+ const normalizedPattern = normalizeOriginPattern(pattern);
30
37
  let parsedPattern;
31
38
  try {
32
- parsedPattern = new URL(pattern.replace(':*', ':0'));
39
+ parsedPattern = new URL(normalizedPattern.replace(':*', ':0'));
33
40
  } catch {
34
41
  return false;
35
42
  }
36
43
  if (parsedPattern.protocol !== origin.protocol) return false;
37
- const rawHost = pattern.replace(/^[a-z]+:\/\//, '').replace(/:\*$/, '').replace(/:\d+$/, '');
38
- const portWildcard = pattern.endsWith(':*');
39
- const explicitPort = pattern.match(/:(\d+)$/)?.[1];
44
+ const rawHost = normalizedPattern.replace(/^[a-z]+:\/\//, '').replace(/:\*$/, '').replace(/:\d+$/, '');
45
+ const portWildcard = normalizedPattern.endsWith(':*');
46
+ const explicitPort = normalizedPattern.match(/:(\d+)$/)?.[1];
40
47
  if (!portWildcard && explicitPort && origin.port !== explicitPort) return false;
41
48
  if (!portWildcard && !explicitPort && origin.port !== parsedPattern.port) return false;
42
49
  if (rawHost.startsWith('*.')) {
@@ -1,4 +1,4 @@
1
- export declare const DEFAULT_ALLOWED_ORIGIN_PATTERNS: readonly ["https://bolt.new", "https://*.bolt.new", "https://stackblitz.com", "https://*.stackblitz.com", "http://localhost:*", "http://127.0.0.1:*"];
1
+ export declare const DEFAULT_ALLOWED_ORIGIN_PATTERNS: readonly ["https://bolt.new", "https://*.bolt.new", "https://stackblitz.com", "https://*.stackblitz.com", "https://*.webcontainer.io", "https://*.local-credentialless.webcontainer.io", "http://localhost:*", "http://127.0.0.1:*"];
2
2
  export interface HostPageBridgeOptions {
3
3
  agentUrl?: string;
4
4
  allowedOrigins?: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,+BAA+B,uJAOlC,CAAC;AAEX,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,wBAAwB;IACvC,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAE,SAAS,MAAM,EAAoC,GAAG,OAAO,CAQtH;AAsBD,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,qBAA0B,GAAG,wBAAwB,CAkFlG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,+BAA+B,sOASlC,CAAC;AAEX,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,wBAAwB;IACvC,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAE,SAAS,MAAM,EAAoC,GAAG,OAAO,CAQtH;AA2BD,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,qBAA0B,GAAG,wBAAwB,CAkFlG"}
package/dist/src/index.js CHANGED
@@ -4,6 +4,8 @@ export const DEFAULT_ALLOWED_ORIGIN_PATTERNS = [
4
4
  'https://*.bolt.new',
5
5
  'https://stackblitz.com',
6
6
  'https://*.stackblitz.com',
7
+ 'https://*.webcontainer.io',
8
+ 'https://*.local-credentialless.webcontainer.io',
7
9
  'http://localhost:*',
8
10
  'http://127.0.0.1:*'
9
11
  ];
@@ -17,19 +19,23 @@ export function isOriginAllowed(origin, patterns = DEFAULT_ALLOWED_ORIGIN_PATTER
17
19
  }
18
20
  return patterns.some((pattern) => originMatchesPattern(parsed, pattern));
19
21
  }
22
+ function normalizeOriginPattern(pattern) {
23
+ return pattern.trim().replace(/\/\*$/, '').replace(/\/$/, '');
24
+ }
20
25
  function originMatchesPattern(origin, pattern) {
26
+ const normalizedPattern = normalizeOriginPattern(pattern);
21
27
  let parsedPattern;
22
28
  try {
23
- parsedPattern = new URL(pattern.replace(':*', ':0'));
29
+ parsedPattern = new URL(normalizedPattern.replace(':*', ':0'));
24
30
  }
25
31
  catch {
26
32
  return false;
27
33
  }
28
34
  if (parsedPattern.protocol !== origin.protocol)
29
35
  return false;
30
- const rawHost = pattern.replace(/^[a-z]+:\/\//, '').replace(/:\*$/, '').replace(/:\d+$/, '');
31
- const portWildcard = pattern.endsWith(':*');
32
- const explicitPort = pattern.match(/:(\d+)$/)?.[1];
36
+ const rawHost = normalizedPattern.replace(/^[a-z]+:\/\//, '').replace(/:\*$/, '').replace(/:\d+$/, '');
37
+ const portWildcard = normalizedPattern.endsWith(':*');
38
+ const explicitPort = normalizedPattern.match(/:(\d+)$/)?.[1];
33
39
  if (!portWildcard && explicitPort && origin.port !== explicitPort)
34
40
  return false;
35
41
  if (!portWildcard && !explicitPort && origin.port !== parsedPattern.port)
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAyB,MAAM,uBAAuB,CAAC;AAE5E,MAAM,CAAC,MAAM,+BAA+B,GAAG;IAC7C,kBAAkB;IAClB,oBAAoB;IACpB,wBAAwB;IACxB,0BAA0B;IAC1B,oBAAoB;IACpB,oBAAoB;CACZ,CAAC;AAaX,MAAM,UAAU,eAAe,CAAC,MAAc,EAAE,WAA8B,+BAA+B;IAC3G,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,oBAAoB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED,SAAS,oBAAoB,CAAC,MAAW,EAAE,OAAe;IACxD,IAAI,aAAkB,CAAC;IACvB,IAAI,CAAC;QACH,aAAa,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,aAAa,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC7D,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC7F,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACnD,IAAI,CAAC,YAAY,IAAI,YAAY,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,KAAK,CAAC;IAChF,IAAI,CAAC,YAAY,IAAI,CAAC,YAAY,IAAI,MAAM,CAAC,IAAI,KAAK,aAAa,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACvF,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAChC,OAAO,MAAM,CAAC,QAAQ,KAAK,MAAM,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,KAAK,OAAO,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,UAAiC,EAAE;IACtE,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,+BAA+B,CAAC;IACrE,MAAM,eAAe,GAAG,OAAO,CAAC,qBAAqB,IAAI,OAAO,CAAC,cAAc,IAAI,CAAC,GAAG,+BAA+B,CAAC,CAAC;IACxH,IAAI,MAA6B,CAAC;IAClC,MAAM,KAAK,GAAuB,EAAE,CAAC;IACrC,MAAM,cAAc,GAAG,IAAI,GAAG,EAAiE,CAAC;IAChG,MAAM,aAAa,GAAG,IAAI,GAAG,EAAiE,CAAC;IAE/F,SAAS,YAAY,CAAC,MAA6C,EAAE,OAAgB,EAAE,MAAc;QACnG,IAAI,MAAM,IAAI,aAAa,IAAI,MAAM,EAAE,CAAC;YACrC,MAAiB,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAChD,OAAO;QACT,CAAC;QACD,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;IAED,SAAS,YAAY;QACnB,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,UAAU,CAAC;YAAE,OAAO,MAAM,CAAC;QAClH,MAAM,GAAG,IAAI,SAAS,CAAC,QAAQ,CAAC,CAAC;QACjC,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE;YACnC,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;gBAAE,MAAO,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;QAC7E,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;YAC3C,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC7D,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC7B,KAAK,MAAM,MAAM,IAAI,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC;oBAC5C,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;gBAC/G,CAAC;gBACD,OAAO;YACT,CAAC;YACD,MAAM,cAAc,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAC/E,YAAY,CACV,cAAc,EAAE,MAAM,EACtB,EAAE,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,EACnE,cAAc,EAAE,MAAM,IAAI,OAAO,CAAC,YAAY,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CACzE,CAAC;YACF,IAAI,OAAO,CAAC,EAAE;gBAAE,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,SAAS,OAAO,CAAC,KAAmB;QAClC,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,MAAM,EAAE,eAAe,CAAC;YAAE,OAAO;QAC5D,MAAM,IAAI,GAAG,KAAK,CAAC,IAA6D,CAAC;QACjF,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,kBAAkB;YAAE,OAAO;QACxD,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QAChF,IAAI,IAAI,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;YACrC,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,qBAAqB,EAAE,QAAQ,EAAE,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;YAChH,OAAO;QACT,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,KAAK,mBAAmB;YAAE,OAAO;QAC9C,IAAI,OAAyB,CAAC;QAC9B,IAAI,CAAC;YACH,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACvC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,WAAW,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,2BAA2B,CAAC;YACzF,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE;gBACzB,MAAM,EAAE,kBAAkB;gBAC1B,IAAI,EAAE,oBAAoB;gBAC1B,OAAO,EAAE;oBACP,eAAe,EAAE,OAAO;oBACxB,IAAI,EAAE,OAAO;oBACb,KAAK,EAAE,EAAE,IAAI,EAAE,wBAAwB,EAAE,OAAO,EAAE,WAAW,EAAE;iBAChE;aACF,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;YACjB,OAAO;QACT,CAAC;QACD,IAAI,OAAO,CAAC,EAAE;YAAE,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QAC/F,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;QAC9B,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI;YAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;;YAC1E,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;IAED,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC5C,OAAO;QACL,KAAK;YACH,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YAC/C,MAAM,EAAE,KAAK,EAAE,CAAC;YAChB,cAAc,CAAC,KAAK,EAAE,CAAC;YACvB,aAAa,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
@@ -24,7 +24,7 @@ async function ensureSocket() {
24
24
  return;
25
25
  }
26
26
  if (message.type === 'event') {
27
- for (const port of ports) port.postMessage({ source: 'bridgekit-extension', type: 'bridgekit.event', message });
27
+ for (const port of ports) safePostToPort(port, { source: 'bridgekit-extension', type: 'bridgekit.event', message });
28
28
  return;
29
29
  }
30
30
  const owner = message.id ? requestOwners.get(message.id) : undefined;
@@ -33,12 +33,12 @@ async function ensureSocket() {
33
33
  pending.delete(message.id);
34
34
  requestOwners.delete(message.id);
35
35
  }
36
- if (owner && ports.has(owner)) owner.postMessage({ source: 'bridgekit-extension', type: 'bridgekit.response', message });
36
+ if (owner && ports.has(owner)) safePostToPort(owner, { source: 'bridgekit-extension', type: 'bridgekit.response', message });
37
37
  else if (callback) callback(message);
38
38
  });
39
39
  socket.addEventListener('close', () => {
40
40
  for (const [id, owner] of requestOwners.entries()) {
41
- if (ports.has(owner)) owner.postMessage({ source: 'bridgekit-extension', type: 'bridgekit.response', message: bridgeError('bridge_closed', 'BridgeKit agent connection closed', id) });
41
+ if (ports.has(owner)) safePostToPort(owner, { source: 'bridgekit-extension', type: 'bridgekit.response', message: bridgeError('bridge_closed', 'BridgeKit agent connection closed', id) });
42
42
  }
43
43
  requestOwners.clear();
44
44
  pending.clear();
@@ -52,30 +52,48 @@ async function isSenderAllowed(sender, pageOrigin) {
52
52
  return Boolean(pageOrigin && senderOrigin === pageOrigin && isOriginAllowed(pageOrigin, policy.allowedOrigins));
53
53
  }
54
54
 
55
+ function forgetPort(port) {
56
+ ports.delete(port);
57
+ for (const [id, owner] of requestOwners.entries()) {
58
+ if (owner === port) requestOwners.delete(id);
59
+ }
60
+ }
61
+
62
+ function safePostToPort(port, message) {
63
+ if (!ports.has(port)) return false;
64
+ try {
65
+ port.postMessage(message);
66
+ return true;
67
+ } catch {
68
+ forgetPort(port);
69
+ return false;
70
+ }
71
+ }
72
+
55
73
  chrome.runtime.onConnect.addListener((port) => {
56
74
  if (port.name !== 'bridgekit-page') return;
57
75
  ports.add(port);
58
76
  port.onDisconnect.addListener(() => {
59
- ports.delete(port);
60
- for (const [id, owner] of requestOwners.entries()) {
61
- if (owner === port) requestOwners.delete(id);
62
- }
77
+ // Reading runtime.lastError clears benign Chrome warnings caused by BFCache
78
+ // or navigation closing a long-lived extension port.
79
+ void chrome.runtime.lastError;
80
+ forgetPort(port);
63
81
  });
64
82
  port.onMessage.addListener((request) => {
65
83
  void (async () => {
66
84
  const pageOrigin = typeof request?.pageOrigin === 'string' ? request.pageOrigin : undefined;
67
85
  if (!request || request.source !== 'bridgekit-client') return;
68
86
  if (!(await isSenderAllowed(port.sender, pageOrigin))) {
69
- port.postMessage({ source: 'bridgekit-extension', type: 'bridgekit.response', message: bridgeError('origin_not_allowed', `BridgeKit origin is not allowed: ${pageOrigin ?? 'unknown'}`, request.message?.id) });
87
+ safePostToPort(port, { source: 'bridgekit-extension', type: 'bridgekit.response', message: bridgeError('origin_not_allowed', `BridgeKit origin is not allowed: ${pageOrigin ?? 'unknown'}`, request.message?.id) });
70
88
  return;
71
89
  }
72
90
  if (request.type === 'bridgekit.detect') {
73
91
  const policy = await getPolicy();
74
- port.postMessage({ source: 'bridgekit-extension', type: 'bridgekit.available', agentUrl: policy.agentUrl, allowed: true });
92
+ safePostToPort(port, { source: 'bridgekit-extension', type: 'bridgekit.available', agentUrl: policy.agentUrl, allowed: true });
75
93
  return;
76
94
  }
77
95
  if (request.type !== 'bridgekit.request' || !validBridgeMessage(request.message)) {
78
- port.postMessage({ source: 'bridgekit-extension', type: 'bridgekit.response', message: bridgeError('invalid_bridge_message', 'Invalid BridgeKit message', request.message?.id) });
96
+ safePostToPort(port, { source: 'bridgekit-extension', type: 'bridgekit.response', message: bridgeError('invalid_bridge_message', 'Invalid BridgeKit message', request.message?.id) });
79
97
  return;
80
98
  }
81
99
  const ws = await ensureSocket();
@@ -86,7 +104,7 @@ chrome.runtime.onConnect.addListener((port) => {
86
104
  if (ws.readyState === WebSocket.OPEN) send();
87
105
  else ws.addEventListener('open', send, { once: true });
88
106
  ws.addEventListener('error', () => {
89
- port.postMessage({ source: 'bridgekit-extension', type: 'bridgekit.response', message: bridgeError('agent_unavailable', 'BridgeKit local agent is not reachable', request.message.id) });
107
+ safePostToPort(port, { source: 'bridgekit-extension', type: 'bridgekit.response', message: bridgeError('agent_unavailable', 'BridgeKit local agent is not reachable', request.message.id) });
90
108
  }, { once: true });
91
109
  })();
92
110
  });
@@ -1,7 +1,41 @@
1
- const port = chrome.runtime.connect({ name: 'bridgekit-page' });
1
+ let port;
2
+ let reconnectTimer;
2
3
  const pageOrigin = window.location.origin;
4
+ const pendingMessages = [];
5
+ let pageIsUnloading = false;
3
6
 
4
- port.onMessage.addListener((message) => {
7
+ function connectPort() {
8
+ if (port) return port;
9
+ try {
10
+ port = chrome.runtime.connect({ name: 'bridgekit-page' });
11
+ } catch {
12
+ scheduleReconnect();
13
+ return undefined;
14
+ }
15
+
16
+ port.onMessage.addListener(handleExtensionMessage);
17
+ port.onDisconnect.addListener(() => {
18
+ // Read runtime.lastError to clear benign Chrome warnings when BFCache,
19
+ // reloads, or navigation close the long-lived message channel.
20
+ void chrome.runtime.lastError;
21
+ port = undefined;
22
+ if (!pageIsUnloading) scheduleReconnect();
23
+ });
24
+
25
+ flushPendingMessages();
26
+ postToExtension({ source: 'bridgekit-client', type: 'bridgekit.detect' });
27
+ return port;
28
+ }
29
+
30
+ function scheduleReconnect() {
31
+ if (reconnectTimer || pageIsUnloading) return;
32
+ reconnectTimer = setTimeout(() => {
33
+ reconnectTimer = undefined;
34
+ connectPort();
35
+ }, 250);
36
+ }
37
+
38
+ function handleExtensionMessage(message) {
5
39
  if (!message || message.source !== 'bridgekit-extension') return;
6
40
  if (message.type === 'bridgekit.event') {
7
41
  window.postMessage({ source: 'bridgekit-bridge', type: 'bridgekit.event', message: message.message }, pageOrigin);
@@ -12,13 +46,61 @@ port.onMessage.addListener((message) => {
12
46
  return;
13
47
  }
14
48
  window.postMessage({ source: 'bridgekit-bridge', type: 'bridgekit.response', message: message.message }, pageOrigin);
15
- });
49
+ }
50
+
51
+ function postToExtension(data) {
52
+ const message = { ...data, pageOrigin };
53
+ const activePort = port ?? connectPort();
54
+ if (!activePort) {
55
+ pendingMessages.push(message);
56
+ return;
57
+ }
58
+ try {
59
+ activePort.postMessage(message);
60
+ } catch {
61
+ port = undefined;
62
+ pendingMessages.push(message);
63
+ scheduleReconnect();
64
+ }
65
+ }
66
+
67
+ function flushPendingMessages() {
68
+ if (!port) return;
69
+ while (pendingMessages.length > 0) {
70
+ const message = pendingMessages.shift();
71
+ try {
72
+ port.postMessage(message);
73
+ } catch {
74
+ port = undefined;
75
+ if (message) pendingMessages.unshift(message);
76
+ scheduleReconnect();
77
+ return;
78
+ }
79
+ }
80
+ }
16
81
 
17
82
  window.addEventListener('message', (event) => {
18
83
  if (event.source !== window || event.origin !== pageOrigin) return;
19
84
  const data = event.data;
20
85
  if (!data || data.source !== 'bridgekit-client') return;
21
- port.postMessage({ ...data, pageOrigin });
86
+ postToExtension(data);
87
+ });
88
+
89
+ window.addEventListener('pagehide', () => {
90
+ pageIsUnloading = true;
91
+ if (reconnectTimer) clearTimeout(reconnectTimer);
92
+ reconnectTimer = undefined;
93
+ try {
94
+ port?.disconnect();
95
+ } catch {
96
+ // Port may already be closed by navigation or BFCache.
97
+ }
98
+ port = undefined;
99
+ });
100
+
101
+ window.addEventListener('pageshow', () => {
102
+ pageIsUnloading = false;
103
+ connectPort();
22
104
  });
23
105
 
24
- port.postMessage({ source: 'bridgekit-client', type: 'bridgekit.detect', pageOrigin });
106
+ connectPort();
@@ -1,10 +1,15 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "BridgeKit Browser Bridge",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
5
5
  "description": "Relays approved BridgeKit requests from WebContainers and browser sandboxes to the local BridgeKit agent.",
6
- "permissions": ["storage"],
7
- "host_permissions": ["http://127.0.0.1/*", "ws://127.0.0.1/*"],
6
+ "permissions": [
7
+ "storage"
8
+ ],
9
+ "host_permissions": [
10
+ "http://127.0.0.1/*",
11
+ "ws://127.0.0.1/*"
12
+ ],
8
13
  "background": {
9
14
  "service_worker": "background.js",
10
15
  "type": "module"
@@ -20,10 +25,14 @@
20
25
  "https://*.bolt.new/*",
21
26
  "https://stackblitz.com/*",
22
27
  "https://*.stackblitz.com/*",
28
+ "https://*.webcontainer.io/*",
29
+ "https://*.local-credentialless.webcontainer.io/*",
23
30
  "http://localhost/*",
24
31
  "http://127.0.0.1/*"
25
32
  ],
26
- "js": ["content-script.js"],
33
+ "js": [
34
+ "content-script.js"
35
+ ],
27
36
  "run_at": "document_start"
28
37
  }
29
38
  ]
@@ -4,6 +4,8 @@ export const DEFAULT_ALLOWED_ORIGINS = [
4
4
  'https://*.bolt.new',
5
5
  'https://stackblitz.com',
6
6
  'https://*.stackblitz.com',
7
+ 'https://*.webcontainer.io',
8
+ 'https://*.local-credentialless.webcontainer.io',
7
9
  'http://localhost:*',
8
10
  'http://127.0.0.1:*'
9
11
  ];
@@ -26,17 +28,22 @@ export function isOriginAllowed(origin, patterns) {
26
28
  return patterns.some((pattern) => originMatchesPattern(parsed, pattern));
27
29
  }
28
30
 
31
+ function normalizeOriginPattern(pattern) {
32
+ return pattern.trim().replace(/\/\*$/, '').replace(/\/$/, '');
33
+ }
34
+
29
35
  function originMatchesPattern(origin, pattern) {
36
+ const normalizedPattern = normalizeOriginPattern(pattern);
30
37
  let parsedPattern;
31
38
  try {
32
- parsedPattern = new URL(pattern.replace(':*', ':0'));
39
+ parsedPattern = new URL(normalizedPattern.replace(':*', ':0'));
33
40
  } catch {
34
41
  return false;
35
42
  }
36
43
  if (parsedPattern.protocol !== origin.protocol) return false;
37
- const rawHost = pattern.replace(/^[a-z]+:\/\//, '').replace(/:\*$/, '').replace(/:\d+$/, '');
38
- const portWildcard = pattern.endsWith(':*');
39
- const explicitPort = pattern.match(/:(\d+)$/)?.[1];
44
+ const rawHost = normalizedPattern.replace(/^[a-z]+:\/\//, '').replace(/:\*$/, '').replace(/:\d+$/, '');
45
+ const portWildcard = normalizedPattern.endsWith(':*');
46
+ const explicitPort = normalizedPattern.match(/:(\d+)$/)?.[1];
40
47
  if (!portWildcard && explicitPort && origin.port !== explicitPort) return false;
41
48
  if (!portWildcard && !explicitPort && origin.port !== parsedPattern.port) return false;
42
49
  if (rawHost.startsWith('*.')) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridgekitux/browser-bridge",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "BridgeKit host-page and browser-extension bridge helpers.",
6
6
  "type": "module",
@@ -18,7 +18,7 @@
18
18
  "package:extension": "node scripts/package-extension.mjs"
19
19
  },
20
20
  "dependencies": {
21
- "@bridgekitux/protocol": "0.1.0"
21
+ "@bridgekitux/protocol": "0.2.0"
22
22
  },
23
23
  "files": [
24
24
  "dist/src",