{"_id":"@duyquangnvx/iframe-rpc","name":"@duyquangnvx/iframe-rpc","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@duyquangnvx/iframe-rpc","version":"0.1.0","description":"Type-safe bidirectional RPC communication between parent window and iframe","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"scripts":{"build":"tsup","dev":"tsup --watch","test":"vitest","test:run":"vitest run","test:coverage":"vitest run --coverage","lint":"eslint src --ext .ts","typecheck":"tsc --noEmit","prepublishOnly":"pnpm run build","demo":"pnpm build && npx serve . -p 3000","demo:open":"pnpm build && npx serve . -p 3000 -o /examples/demo/parent.html"},"keywords":["iframe","postMessage","rpc","bridge","communication","type-safe","typescript","micro-frontend"],"author":{"name":"duyquangnvx"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/duyquangnvx/iframe-rpc.git"},"bugs":{"url":"https://github.com/duyquangnvx/iframe-rpc/issues"},"homepage":"https://github.com/duyquangnvx/iframe-rpc#readme","publishConfig":{"access":"public"},"engines":{"node":">=18.0.0"},"devDependencies":{"@types/node":"^20.10.0","@typescript-eslint/eslint-plugin":"^7.0.0","@typescript-eslint/parser":"^7.0.0","@vitest/coverage-v8":"^1.2.0","eslint":"^8.56.0","jsdom":"^24.0.0","tsup":"^8.0.0","typescript":"^5.3.0","vitest":"^1.2.0"},"_id":"@duyquangnvx/iframe-rpc@0.1.0","gitHead":"32224a8b0805cffcb68f13893ad49d2f4be2a620","_nodeVersion":"22.21.1","_npmVersion":"10.9.4","dist":{"integrity":"sha512-GClaXaAaDfWWREAA223iVxnppBA51Aogah8zIu/19phHARgdVEoEL20jpAu4lRljbJnm3BtcExXmHp5LsRgWYg==","shasum":"ce9ca469ebb4eece5c1c02fcd2feefce8a9f2ced","tarball":"https://registry.npmjs.org/@duyquangnvx/iframe-rpc/-/iframe-rpc-0.1.0.tgz","fileCount":9,"unpackedSize":99287,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIDQxAaPyAdDhoK9Jy/oDmokztKo8YJCQ8vQrUbAakGqtAiB3ybnI1Gm4Eeegj4YROmNA8w9qWo4v+upNm4vktTS9sw=="}]},"_npmUser":{"name":"duyquangnvx","email":"duyquangnvx@gmail.com"},"directories":{},"maintainers":[{"name":"duyquangnvx","email":"duyquangnvx@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/iframe-rpc_0.1.0_1768641014656_0.8776054002181046"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-17T09:10:14.554Z","0.1.0":"2026-01-17T09:10:14.824Z","modified":"2026-01-17T09:10:15.036Z"},"maintainers":[{"name":"duyquangnvx","email":"duyquangnvx@gmail.com"}],"description":"Type-safe bidirectional RPC communication between parent window and iframe","homepage":"https://github.com/duyquangnvx/iframe-rpc#readme","keywords":["iframe","postMessage","rpc","bridge","communication","type-safe","typescript","micro-frontend"],"repository":{"type":"git","url":"git+https://github.com/duyquangnvx/iframe-rpc.git"},"author":{"name":"duyquangnvx"},"bugs":{"url":"https://github.com/duyquangnvx/iframe-rpc/issues"},"license":"MIT","readme":"# @duyquangnvx/iframe-rpc\r\n\r\nType-safe bidirectional RPC communication between parent window and iframe.\r\n\r\n## Features\r\n\r\n- **Type-safe RPC**: Full TypeScript inference for method parameters and return types\r\n- **Bidirectional**: Both parent and iframe can call methods on each other\r\n- **Fire-and-forget**: Support for one-way notifications without waiting for response\r\n- **Timeout handling**: Configurable timeouts with automatic cleanup\r\n- **Channel isolation**: Multiple independent bridges on the same page\r\n- **Zero dependencies**: Lightweight with no runtime dependencies\r\n\r\n## Installation\r\n\r\n```bash\r\nnpm install @duyquangnvx/iframe-rpc\r\n# or\r\npnpm add @duyquangnvx/iframe-rpc\r\n# or\r\nyarn add @duyquangnvx/iframe-rpc\r\n```\r\n\r\n## Quick Start\r\n\r\n### 1. Define your API contracts\r\n\r\n```typescript\r\n// Shared types (e.g., shared/types.ts)\r\ntype ParentMethods = {\r\n  getUser: (id: string) => Promise<{ name: string; age: number }>;\r\n  notify: (message: string) => void;\r\n};\r\n\r\ntype IframeMethods = {\r\n  initialize: (config: { theme: string }) => Promise<void>;\r\n  getStatus: () => Promise<'ready' | 'loading'>;\r\n};\r\n```\r\n\r\n### 2. Set up the parent window\r\n\r\n```typescript\r\nimport { createParentBridge } from '@duyquangnvx/iframe-rpc';\r\n\r\nconst iframe = document.getElementById('my-iframe') as HTMLIFrameElement;\r\n\r\nconst bridge = createParentBridge<ParentMethods, IframeMethods>(iframe, {\r\n  getUser: async (id) => ({ name: 'John', age: 30 }),\r\n  notify: (message) => console.log('Notification:', message),\r\n});\r\n\r\n// Call iframe methods with full type safety\r\nconst status = await bridge.call.getStatus();\r\nawait bridge.call.initialize({ theme: 'dark' });\r\n```\r\n\r\n### 3. Set up the iframe\r\n\r\n```typescript\r\nimport { createIframeBridge } from '@duyquangnvx/iframe-rpc';\r\n\r\nconst bridge = createIframeBridge<IframeMethods, ParentMethods>({\r\n  initialize: async (config) => {\r\n    document.body.className = config.theme;\r\n  },\r\n  getStatus: async () => 'ready',\r\n});\r\n\r\n// Call parent methods with full type safety\r\nconst user = await bridge.call.getUser('123');\r\nbridge.notify('logEvent', 'iframe-loaded'); // Fire-and-forget\r\n```\r\n\r\n## API Reference\r\n\r\n### `createParentBridge<TLocal, TRemote>(iframe, handlers, options?)`\r\n\r\nCreates a bridge in the parent window to communicate with an iframe.\r\n\r\n**Parameters:**\r\n- `iframe`: `HTMLIFrameElement` - The iframe to communicate with\r\n- `handlers`: `TLocal` - Object containing methods the iframe can call\r\n- `options?`: `BridgeOptions` - Configuration options\r\n\r\n**Returns:** `Bridge<TLocal, TRemote>`\r\n\r\n### `createIframeBridge<TLocal, TRemote>(handlers, options?)`\r\n\r\nCreates a bridge in the iframe to communicate with the parent window.\r\n\r\n**Parameters:**\r\n- `handlers`: `TLocal` - Object containing methods the parent can call\r\n- `options?`: `BridgeOptions` - Configuration options\r\n\r\n**Returns:** `Bridge<TLocal, TRemote>`\r\n\r\n### `BridgeOptions`\r\n\r\n```typescript\r\ninterface BridgeOptions {\r\n  timeout?: number;       // RPC timeout in ms (default: 30000)\r\n  targetOrigin?: string;  // postMessage target origin (default: '*')\r\n  channel?: string;       // Channel name for isolation (default: 'default')\r\n  debug?: boolean;        // Enable debug logging (default: false)\r\n}\r\n```\r\n\r\n### `Bridge<TLocal, TRemote>`\r\n\r\n```typescript\r\ninterface Bridge<TLocal, TRemote> {\r\n  call: CallProxy<TRemote>;           // Type-safe proxy for calling remote methods\r\n  invoke: (method, ...args) => Promise; // Call by method name (for dynamic calls)\r\n  notify: (method, ...args) => void;  // Fire-and-forget calls\r\n  destroy: () => void;                // Clean up and stop listening\r\n  isActive: () => boolean;            // Check if bridge is active\r\n}\r\n```\r\n\r\n### Calling Methods\r\n\r\nTwo ways to call remote methods:\r\n\r\n```typescript\r\n// 1. Proxy API (recommended) - best IDE support\r\nconst user = await bridge.call.getUser('123');\r\n\r\n// 2. Invoke API - for dynamic method names\r\nconst methodName = 'getUser';\r\nconst user = await bridge.invoke(methodName, '123');\r\n```\r\n\r\nBoth are fully type-safe. Use `call` for static calls, `invoke` when method name is dynamic.\r\n\r\n## Error Handling\r\n\r\nThe library provides typed error classes:\r\n\r\n```typescript\r\nimport { RpcError, RpcTimeoutError, RpcMethodNotFoundError } from '@duyquangnvx/iframe-rpc';\r\n\r\ntry {\r\n  await bridge.call.someMethod();\r\n} catch (error) {\r\n  if (error instanceof RpcTimeoutError) {\r\n    console.log('Call timed out');\r\n  } else if (error instanceof RpcMethodNotFoundError) {\r\n    console.log('Method not found on remote side');\r\n  } else if (error instanceof RpcError) {\r\n    console.log('RPC error:', error.message, error.code);\r\n  }\r\n}\r\n```\r\n\r\n## Channel Isolation\r\n\r\nRun multiple independent bridges on the same page:\r\n\r\n```typescript\r\n// Widget A\r\nconst bridgeA = createParentBridge(iframeA, handlersA, { channel: 'widget-a' });\r\n\r\n// Widget B (won't interfere with Widget A)\r\nconst bridgeB = createParentBridge(iframeB, handlersB, { channel: 'widget-b' });\r\n```\r\n\r\n## Security Considerations\r\n\r\nBy default, `targetOrigin` is set to `'*'` which allows communication with any origin. For production, you should specify the exact origin:\r\n\r\n```typescript\r\nconst bridge = createParentBridge(iframe, handlers, {\r\n  targetOrigin: 'https://trusted-domain.com',\r\n});\r\n```\r\n\r\n## License\r\n\r\nMIT\r\n","readmeFilename":"README.md","_rev":"1-20bbef7aa8693aaa36c39ba260b0dd06"}