chrome-in-iframe 2.0.0 → 2.1.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.
package/README.zh-CN.md CHANGED
@@ -10,17 +10,17 @@
10
10
 
11
11
  ## 模块格式
12
12
 
13
- **本包只提供 ESM。** 发布产物是单个 ECMAScript 模块,没有 CommonJS 入口。请在 ESM 项目中使用(Vite、Rollup、webpack 5+、Parcel 2+、其他现代打包器,或浏览器原生 `<script type="module">`)。
13
+ 本包同时提供 ESM CommonJS 入口。现代打包器和原生模块代码会自动使用 ESM 入口;CommonJS 消费方可以使用 `require()`。
14
14
 
15
- `require('chrome-in-iframe')` 无法工作。
15
+ ```ts
16
+ import { connectChromeInIframe, exposeChromeInIframe } from 'chrome-in-iframe';
17
+ ```
16
18
 
17
- ```json
18
- {
19
- "type": "module"
20
- }
19
+ ```js
20
+ const { connectChromeInIframe, exposeChromeInIframe } = require('chrome-in-iframe');
21
21
  ```
22
22
 
23
- 如果你的构建工具仍然产出 CommonJS,请将 `chrome-in-iframe` 配置为 ESM external,或将消费方项目迁移到 ESM。
23
+ CommonJS 构建会按浏览器条件解析依赖,因此仍可被打包到 Chrome 扩展页面和 iframe 中使用。
24
24
 
25
25
  ## 安装
26
26
 
@@ -147,11 +147,13 @@ window.addEventListener('beforeunload', () => {
147
147
 
148
148
  ## 安全选项
149
149
 
150
- `targetOrigin` 和 `allowedOrigin` 是可选的。当不传 `targetOrigin` 时,库会自动从 `iframe.src` / `contentWindow.location.origin`(扩展侧)或 `document.referrer` / `location.ancestorOrigins`(iframe 侧)推导出 origin;只有在以上来源都不可用时才回退到 `'*'`。传入的消息还会通过预期的 window source 进行校验。
150
+ `targetOrigin` 和 `allowedOrigin` 是可选的。当不传 `targetOrigin` 时,库会自动从 `iframe.src` / `contentWindow.location.origin`(扩展侧)或 `document.referrer` / `location.ancestorOrigins`(iframe 侧)推导出 origin;只有在以上来源都不可用时才回退到 `'*'`。传入的消息默认同时校验预期的 window source,以及为 `targetOrigin` 推导出的同一个 origin;当 `targetOrigin` 解析失败回退到 `'*'` 时,`allowedOrigin` 默认收紧为 `event.origin === window.location.origin`(同源限制),而不是接受任意 origin。
151
151
 
152
- > **警告**:当两侧都是同一 `chrome-extension://` origin 下的 Chrome 扩展页面时,默认行为是安全的。但如果 iframe 加载的是**第三方页面**或来自**不同 origin** 的页面,不设置 `allowedOrigin` 意味着**任何能向页面发送消息的窗口**都可以伪造 `postMessage` 来冒充 Chrome API 调用。在这种情况下,务必将 `allowedOrigin` 设置为预期的 origin。
152
+ > **提示**:`'*'` 回退仅影响 `postMessage` 出去的 `targetOrigin`(可能被中间人窥探),不会让任意 origin 的消息打进来。对于第三方 iframe、可能被导航的 iframe,或任何不同 origin 的页面,仍建议显式传入 `targetOrigin` `allowedOrigin`,以便消息可以发往明确的目标 origin。
153
153
 
154
- 在生产环境中,如果知道具体来源,建议显式传入 origin。
154
+ > **注意**:`targetOrigin` 的自动推导结果会被冻结(`freezeResolvedOrigin`),首次成功解析后不会再变;但首次解析回退到 `'*'` 时不冻结,下次调用会重试,以兼容「先 `createElement('iframe')` 再设 `src`」这种常见模式。
155
+
156
+ 在生产环境中,如果知道具体来源,建议显式传入 origin。
155
157
 
156
158
  ### 扩展页面
157
159
 
@@ -180,10 +182,10 @@ const { chrome } = connectChromeInIframe({
180
182
 
181
183
  任何 RPC 桥接都有的固有限制:
182
184
 
183
- - **对象身份不被保留。** 跨桥的 `Date`、`Map`、`Set`、普通对象会在对面被重新构造。原对象和 round-trip 后的对象之间 `===` 为 `false`。
184
- - **以对象为 key 的 Map / Set 会丢失查找能力。** 把 `Date`(或任何对象)作为 `Map` 的 key,跨桥之后,对面 `map.get(originalDate)` 找不到——反序列化得到的 `Map` 里持有的是另一个 `Date` 实例。需要跨桥传输的 `Map`,请用字符串或数字作 key。
185
- - **非全局 symbol 不能跨桥。** 用 `Symbol.for(...)` 或 well-known symbol;临时 `Symbol('local')` 在序列化时抛错。
186
- - **类原型会被擦除。** `class Foo { ... }` 的实例到对面只是一个普通对象,只保留可枚举字段;`instanceof Foo` 为 `false`。
185
+ - **对象身份不被保留。** 跨桥的 `Date`、`Map`、`Set`、普通对象会在对端被重新构造。原对象和往返后的对象之间 `===` 为 `false`。
186
+ - **以对象为 key 的 Map / Set 会丢失查找能力。** 把 `Date`(或任何对象)作为 `Map` 的 key,跨桥之后,对端 `map.get(originalDate)` 找不到 —— 反序列化得到的 `Map` 里持有的是另一个 `Date` 实例。需要跨桥传输的 `Map`,请用字符串或数字作 key。
187
+ - **非全局 symbol 不能跨桥。** 用 `Symbol.for(...)` 或 well-known symbol;临时 `Symbol('local')` 在序列化时抛错。
188
+ - **类原型会被擦除。** `class Foo { ... }` 的实例到对端只是一个普通对象,只保留可枚举字段;`instanceof Foo` 为 `false`。
187
189
 
188
190
  ## 读取属性
189
191
 
@@ -267,11 +269,11 @@ const bridge = exposeChromeInIframe({
267
269
  - `iframe`:要桥接的 iframe 元素。
268
270
  - `key`:可选的共享桥接 key。两侧使用相同的 key。
269
271
  - `targetOrigin`:可选,`postMessage` 使用的 origin。不传时由 `iframe.src` 推导。
270
- - `allowedOrigin`:可选,用于过滤传入消息的 origin、origin 列表或判断函数。
272
+ - `allowedOrigin`:可选,用于过滤传入消息;可以是 origin、origin 列表或判断函数。默认情况下:解析后的 `targetOrigin` 是具体 origin 时,使用它;若回退到 `'*'`,则收紧为同源限制(`event.origin === window.location.origin`)。
271
273
  - `chromeApi`:可选,自定义的 Chrome API 对象。默认为 `globalThis.chrome`。
272
274
  - `timeout`:可选,单次调用的超时时长(毫秒,默认 `30000`)。
273
- - `functionCacheMax` / `functionCacheTtl`:调整本地存储的非持久 callback LRU
274
- - `remoteCallbackCacheMax` / `remoteCallbackCacheTtl`:调整对端交给我们的 callback LRU
275
+ - `functionCacheMax` / `functionCacheTtl`:调整本端发出的非持久 callback LRU 缓存。
276
+ - `remoteCallbackCacheMax` / `remoteCallbackCacheTtl`:调整对端发给本端的 callback LRU 缓存。
275
277
 
276
278
  返回值:
277
279
 
@@ -302,3 +304,19 @@ const { chrome, get, destroy } = connectChromeInIframe({
302
304
  - `proxy`:与 `chrome` 相同的对象。
303
305
  - `get(path)`:从桥接的 Chrome API 中读取属性。详见 [路径形式](#路径形式)。
304
306
  - `destroy()`:移除消息监听器,清理桥接状态,并通知对端。
307
+
308
+ ### `setLogger(logger)`
309
+
310
+ 自定义或静默库内警告。所有内部警告均带有 `[chrome-in-iframe]` 前缀和 scope 标签。
311
+
312
+ ```ts
313
+ import { setLogger } from 'chrome-in-iframe';
314
+
315
+ // 静默所有警告
316
+ setLogger(null);
317
+
318
+ // 将警告路由到自定义 sink(如 Sentry)
319
+ setLogger((scope, ...args) => {
320
+ captureMessage(`[chrome-in-iframe] ${scope}`, { extra: { args } });
321
+ });
322
+ ```
package/dist/api.d.ts CHANGED
@@ -11,6 +11,7 @@ export interface ConnectionOptions {
11
11
  timeout?: number;
12
12
  targetOrigin?: string;
13
13
  allowedOrigin?: AllowedOrigin;
14
+ strictOrigin?: boolean;
14
15
  functionCacheMax?: number;
15
16
  functionCacheTtl?: number;
16
17
  remoteCallbackCacheMax?: number;
@@ -29,6 +30,7 @@ export interface ConnectionHandle<T> {
29
30
  proxy: T & PropertyAccess;
30
31
  get: PropertyAccess['$get'];
31
32
  destroy: () => void;
33
+ isDestroyed: () => boolean;
32
34
  }
33
35
  export interface ChromeIframeHandle<T = ChromeApi> extends ConnectionHandle<T> {
34
36
  chrome: T & PropertyAccess;
@@ -1,3 +1,7 @@
1
- import type { ProcessorRegistry } from '../processor/registry';
2
- import type { ClientContext, MessageChannel, MessagePoster } from './types';
1
+ import type { ProcessorRegistry } from '../processor/registry.js';
2
+ import type { ClientContext, MessageChannel, MessagePoster } from './types.js';
3
+ export declare const MAX_PATH_LENGTH = 64;
4
+ export declare const MAX_INVOKE_ARGS = 1024;
5
+ export declare const MAX_MESSAGE_LENGTH = 1000000;
6
+ export declare const MAX_RELEASE_IDS = 10000;
3
7
  export declare function createMessageChannel(poster: MessagePoster, key: string, instanceId: string, context: ClientContext, processorRegistry: ProcessorRegistry): MessageChannel;
@@ -1,4 +1,10 @@
1
- import type { Callable, CallbackCacheOptions, MessageValue } from './types';
1
+ import type { Callable, CallbackCacheOptions, MessageValue } from './types.js';
2
2
  export type GenerateCallbackFn = (id: string, args: MessageValue[], options?: CallbackCacheOptions) => MessageValue;
3
3
  export type GetRemoteCallbackFn = (id: string, invoke: (args: MessageValue[]) => MessageValue, options?: CallbackCacheOptions) => Callable;
4
+ export declare const MAX_DESERIALIZE_DEPTH = 200;
4
5
  export declare function deserialize(arg: MessageValue, generateCallback: GenerateCallbackFn, getRemoteCallback?: GetRemoteCallbackFn): MessageValue;
6
+ export declare function rebuildErrorFromSerialized(serialized: {
7
+ message: string;
8
+ name?: string;
9
+ stack?: string;
10
+ }): Error;
@@ -1,4 +1,4 @@
1
- import type { PathKey } from './types';
1
+ import type { PathKey } from './types.js';
2
2
  export declare function isListenerRegistrationPath(path: PathKey[]): boolean;
3
3
  export declare function isListenerRemovalPath(path: PathKey[]): boolean;
4
4
  export declare function isLikelyListenerPath(path: PathKey[]): boolean;
@@ -1,4 +1,4 @@
1
- import type { PathKey } from './types';
1
+ import type { PathKey } from './types.js';
2
2
  type SerializedSymbolPathKey = {
3
3
  $type: 'symbol';
4
4
  value: string;
@@ -1,2 +1,2 @@
1
- import type { MessagePoster, MessageSender } from './types';
1
+ import type { MessagePoster, MessageSender } from './types.js';
2
2
  export declare function createMessageSender(poster: MessagePoster, key: string, instanceId: string): MessageSender;
@@ -1,4 +1,7 @@
1
- import type { Callable, CallbackCacheOptions, MessageValue } from './types';
1
+ import type { Callable, CallbackCacheOptions, MessageValue } from './types.js';
2
+ import { setOwnProperty } from './utils.js';
2
3
  export declare const TYPE_KEY = "$cii$";
4
+ export declare const MAX_SERIALIZE_DEPTH = 200;
3
5
  export type RegisterFunctionFn = (fn: Callable, thisArg: MessageValue, options?: CallbackCacheOptions) => string;
4
6
  export declare function serialize(arg: MessageValue, registerFunction: RegisterFunctionFn, options?: CallbackCacheOptions): MessageValue;
7
+ export { setOwnProperty };
@@ -7,10 +7,22 @@ export type SerializedError = {
7
7
  message: string;
8
8
  stack?: string;
9
9
  };
10
+ export declare const TRANSPORT_DETACHED: unique symbol;
11
+ export interface TransportDetachedError extends Error {
12
+ [TRANSPORT_DETACHED]: true;
13
+ }
14
+ export declare function isTransportDetachedError(err: unknown): err is TransportDetachedError;
15
+ export declare function createTransportDetachedError(message: string): TransportDetachedError;
10
16
  export type ListenerCallback = (event: {
11
17
  data: string;
12
18
  }) => void;
13
19
  export interface Messages {
20
+ connectRequest: {
21
+ instanceId: string;
22
+ };
23
+ connectResponse: {
24
+ instanceId: string;
25
+ };
14
26
  invokeRequest: {
15
27
  id: RequestId;
16
28
  path: PathKey[];
@@ -63,7 +75,10 @@ export interface MessagePoster {
63
75
  export interface PromiseCallbacks {
64
76
  resolve: (value: MessageValue) => void;
65
77
  reject: (reason: MessageValue) => void;
66
- timer: ReturnType<typeof setTimeout>;
78
+ timer: ReturnType<typeof setTimeout> | null;
79
+ onResolve?: () => void;
80
+ onReject?: () => void;
81
+ expectedRemote?: string;
67
82
  }
68
83
  export interface ClientContext {
69
84
  getDelegateTarget(): MessageValue;
@@ -72,13 +87,26 @@ export interface ClientContext {
72
87
  getFunctionCache(): LRUCache<string, CallbackEntry>;
73
88
  getPersistentFunctionCache(): Map<string, CallbackEntry>;
74
89
  registerFunction(fn: Callable, thisArg: MessageValue, options?: CallbackCacheOptions): string;
90
+ serializeForRemote(value: MessageValue, target: 'broadcast' | string, options?: CallbackCacheOptions): MessageValue;
75
91
  getRemoteCallback(id: string, invoke: (args: MessageValue[]) => MessageValue, options: CallbackCacheOptions | undefined, remoteInstanceId: string): Callable;
76
- getAndRemovePendingPromise(id: RequestId): PromiseCallbacks | undefined;
92
+ getAndRemovePendingPromise(id: RequestId, senderInstanceId?: string): PromiseCallbacks | undefined;
77
93
  invoke(path: PathKey[], args: MessageValue[]): Promise<MessageValue>;
78
94
  accessProperty(path: PathKey[]): Promise<MessageValue>;
79
- invokeFunctionById(id: string, args: MessageValue[], options?: CallbackCacheOptions): Promise<MessageValue>;
80
- registerPendingPromise(id: RequestId, callbacks: PromiseCallbacks): void;
95
+ invokeFunctionById(id: string, args: MessageValue[], options?: CallbackCacheOptions, remoteInstanceId?: string): Promise<MessageValue>;
81
96
  handleRemoteDestroy(instanceId: string): void;
97
+ bindRemote(remoteInstanceId: string): void;
98
+ noteFreshConnect(remoteInstanceId: string): void;
99
+ disableRemoteTargetWait(): void;
100
+ noteRemoteSeen(remoteInstanceId: string): void;
101
+ releaseRemoteCallbacks(remoteInstanceId: string, ids: readonly string[]): void;
102
+ dropLocalCallback(id: string, requesterInstanceId?: string): void;
103
+ bindMethod(fn: Callable, owner: MessageValue): Callable;
104
+ /** @internal Exposed for tests; do not use in application code. */
105
+ getRemoteCallbackCounts(remoteInstanceId: string): {
106
+ lru: number;
107
+ persistent: number;
108
+ };
109
+ isDestroyed(): boolean;
82
110
  destroy(notifyRemote?: boolean): void;
83
111
  }
84
112
  export interface CallbackCacheOptions {
@@ -90,10 +118,6 @@ export interface CallbackEntry {
90
118
  }
91
119
  export interface MessageChannel {
92
120
  getSender(): MessageSender;
93
- getContext(): ClientContext;
94
- getPoster(): MessagePoster;
95
- getKey(): string;
96
- getInstanceId(): string;
97
121
  destroy(): void;
98
122
  }
99
123
  export interface MessageSender {
@@ -1,5 +1,9 @@
1
- import type { SerializedError } from './types';
1
+ import type { MessageValue, SerializedError } from './types.js';
2
2
  export declare const WELL_KNOWN_SYMBOLS: Record<string, symbol>;
3
3
  export declare function getWellKnownSymbolName(value: symbol): string | undefined;
4
4
  export declare function getWellKnownSymbol(name: string): symbol | undefined;
5
+ export declare function encodeSymbolToken(value: symbol, context?: string): string;
6
+ export declare function decodeSymbolToken(value: string, context?: string): symbol;
7
+ export declare function setOwnProperty(target: Record<PropertyKey, MessageValue>, key: PropertyKey, value: MessageValue): void;
5
8
  export declare function serializeThrownError(err: unknown): SerializedError;
9
+ export declare function isPromiseLike(value: unknown): value is PromiseLike<unknown>;
@@ -1,4 +1,4 @@
1
- import type { ClientContext, MessageChannel, MessageValue } from '../channel/types';
1
+ import type { ClientContext, MessageChannel, MessageValue } from '../channel/types.js';
2
2
  export interface ClientContextOptions {
3
3
  delegateTarget?: MessageValue;
4
4
  timeout?: number;
@@ -1,5 +1,5 @@
1
- import type { ClientContext, MessagePoster } from '../channel/types';
2
- import { type ClientContextOptions } from './context';
1
+ import type { ClientContext, MessagePoster } from '../channel/types.js';
2
+ import { type ClientContextOptions } from './context.js';
3
3
  export interface EndpointOptions {
4
4
  poster: MessagePoster;
5
5
  key?: string;
@@ -8,6 +8,7 @@ export interface EndpointOptions {
8
8
  export interface Endpoint<TProxy = unknown> {
9
9
  proxy: TProxy;
10
10
  destroy: () => void;
11
+ isDestroyed: () => boolean;
11
12
  /** @internal Exposed for tests; do not use in application code. */
12
13
  getContext: () => ClientContext;
13
14
  }
@@ -1,3 +1,3 @@
1
- import type { MessageValue, PathKey, ProxyNode } from '../channel/types';
1
+ import type { MessageValue, PathKey, ProxyNode } from '../channel/types.js';
2
2
  export declare function resolvePath(node: ProxyNode): PathKey[];
3
3
  export declare function createClientProxy<TProxy = unknown>(invoke: (path: PathKey[], args: MessageValue[]) => Promise<MessageValue>, accessProperty?: (path: PathKey[]) => Promise<MessageValue>): TProxy;