{"id":31577550,"url":"https://github.com/addon-stack/inject-script","last_synced_at":"2026-02-15T16:31:26.729Z","repository":{"id":300215805,"uuid":"1005541294","full_name":"addon-stack/inject-script","owner":"addon-stack","description":"A lightweight, TypeScript-ready library for injecting JavaScript functions or external scripts into Chrome extension tabs and frames (Manifest V2 \u0026 V3).","archived":false,"fork":false,"pushed_at":"2026-01-17T21:04:19.000Z","size":146,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-18T07:51:00.824Z","etag":null,"topics":["chrome","chrome-extension","edge","firefox","opera","safari","script"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/addon-stack.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-06-20T11:45:43.000Z","updated_at":"2026-01-17T21:05:30.000Z","dependencies_parsed_at":"2026-01-17T23:05:25.465Z","dependency_job_id":null,"html_url":"https://github.com/addon-stack/inject-script","commit_stats":null,"previous_names":["addonbone/inject-script","addon-stack/inject-script"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/addon-stack/inject-script","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/addon-stack%2Finject-script","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/addon-stack%2Finject-script/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/addon-stack%2Finject-script/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/addon-stack%2Finject-script/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/addon-stack","download_url":"https://codeload.github.com/addon-stack/inject-script/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/addon-stack%2Finject-script/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29484653,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-15T15:33:17.885Z","status":"ssl_error","status_checked_at":"2026-02-15T15:32:53.698Z","response_time":118,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chrome","chrome-extension","edge","firefox","opera","safari","script"],"created_at":"2025-10-05T19:00:18.405Z","updated_at":"2026-02-15T16:31:26.723Z","avatar_url":"https://github.com/addon-stack.png","language":"TypeScript","readme":"# @addon-core/inject-script\n\n[![npm version](https://img.shields.io/npm/v/%40addon-core%2Finject-script.svg?logo=npm)](https://www.npmjs.com/package/@addon-core/inject-script)\n[![npm downloads](https://img.shields.io/npm/dm/%40addon-core%2Finject-script.svg)](https://www.npmjs.com/package/@addon-core/inject-script)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE.md)\n[![CI](https://github.com/addon-stack/inject-script/actions/workflows/ci.yml/badge.svg)](https://github.com/addon-stack/inject-script/actions/workflows/ci.yml)\n\nA lightweight, TypeScript-ready library for injecting JavaScript functions and external script files into browser extension pages. It automatically detects Manifest V2/V3 and uses the appropriate API implementation.\n\n## Installation\n\n### npm:\n\n```bash\nnpm install @addon-core/inject-script\n```\n\n### pnpm:\n\n```bash\npnpm add @addon-core/inject-script\n```\n\n### yarn:\n\n```bash\nyarn add @addon-core/inject-script\n```\n\n## Quick Start\n\n```ts\nimport injectScript, { type InjectScriptOptions } from \"@addon-core/inject-script\";\n\n// Initialize an injector for a specific tab\nconst injector = injectScript({\n  tabId: 123,\n  frameId: false,          // top frame only\n  matchAboutBlank: true,   // include about:blank and similar pages\n  runAt: \"document_idle\",  // injection timing (MV2)\n  // timeFallback: 5000,   // (MV2) default timeout is 4000 ms\n  // world: 'ISOLATED',    // (MV3) execution world\n  // documentId: 'abc123', // (MV3) target by documentId\n} satisfies InjectScriptOptions);\n\n// Execute a function in the page context (for all target frames)\nconst results = await injector.run(\n  (msg: string) =\u003e {\n    console.log(msg);\n    return `Echo: ${msg}`;\n  },\n  [\"Hello from the extension!\"]\n);\n\n// Inject one or more external files\nawait injector.file(\"scripts/content.js\");\nawait injector.file([\"scripts/lib.js\", \"scripts/util.js\"]);\n```\n\n## Features\n\n- Unified API for Manifest V2 and V3 (version detection via `@addon-core/browser`).\n- Inject functions (`run`) and files (`file`).\n- Precise targeting: top frame, specific `frameId[]`, all frames, or (MV3) `documentId[]`.\n- `world` support (MV3): `MAIN`/`ISOLATED`; instant injection when `runAt: 'document_start'`.\n- Strongly-typed results: returns an array of `InjectionResult\u003cAwaited\u003cR\u003e\u003e` (one per frame).\n- Update options on the fly with `options()`.\n\n## API\n\n### `injectScript(options: InjectScriptOptions): InjectScriptContract`\n\nCreates and returns a new script injector. The implementation is chosen internally based on your manifest (MV2/MV3).\n\n#### Contract\n\n```ts\ninterface InjectScriptContract {\n  run\u003cA extends any[], R\u003e(\n    func: (...args: A) =\u003e R,\n    args?: A\n  ): Promise\u003cchrome.scripting.InjectionResult\u003cchrome.scripting.Awaited\u003cR\u003e\u003e[]\u003e;\n\n  file(files: string | string[]): Promise\u003cvoid\u003e;\n\n  options(options: Partial\u003cInjectScriptOptions\u003e): this;\n}\n```\n\n#### Options\n\n```ts\ninterface InjectScriptOptions {\n  tabId: number;\n  frameId?: boolean | number | number[];\n  matchAboutBlank?: boolean;   // defaults to true (MV2/MV3)\n\n  // MV2\n  runAt?: chrome.extensionTypes.RunAt; // 'document_start' | 'document_end' | 'document_idle'\n  timeFallback?: number;               // timeout in ms, default 4000\n\n  // MV3\n  world?: chrome.scripting.ExecutionWorld | `${chrome.scripting.ExecutionWorld}`; // 'MAIN' | 'ISOLATED'\n  documentId?: string | string[];      // Firefox does not support documentIds in target\n}\n```\n\n- `frameId`: `true` — all frames; a number or array — specific `frameId`s; `false` or undefined — top frame only.\n- `matchAboutBlank`: if omitted, the library enables it by default (`true`).\n- `runAt`: Chrome default is `document_idle` (when not specified).\n- `timeFallback` (MV2): if results do not arrive in time, the promise will be rejected with an error.\n- `world` (MV3): sets the execution world. When `runAt: 'document_start'`, `injectImmediately` is enabled.\n- `documentId` (MV3): target specific documents. Firefox does not support `documentIds`; the library gracefully avoids using them there.\n\n## Examples\n\nInject into specific frames:\n\n```ts\nconst injector = injectScript({ tabId: 123, frameId: [0, 2] });\nconst results = await injector.run(() =\u003e window.location.href);\nconsole.log(results.map(r =\u003e ({ frameId: r.frameId, url: r.result })));\n```\n\nAll frames:\n\n```ts\nawait injectScript({ tabId: 123, frameId: true }).file([\"a.js\", \"b.js\"]);\n```\n\nMV3: target by documentId and choose execution world:\n\n```ts\nawait injectScript({\n  tabId: 123,\n  documentId: [\"doc-1\", \"doc-2\"],\n  world: \"MAIN\",\n}).run(() =\u003e ({ ready: document.readyState }));\n```\n\nUpdate options on the fly:\n\n```ts\nconst inj = injectScript({ tabId: 123, frameId: false });\nawait inj.options({ frameId: true }).file(\"content.js\");\n```\n\n## MV2/MV3 Compatibility\n\n- MV2: the library serializes your function and arguments, executes the code in target frames, and returns results via `chrome.runtime.sendMessage`.\n  - If your function throws, the result for that frame will be `undefined`, and the error will be logged in the page DevTools console.\n  - Timeout is controlled by the `timeFallback` option (default 4000 ms).\n  - Result order: the top frame (`frameId = 0`) is the first element in the array.\n- MV3: uses `chrome.scripting.executeScript` with a properly constructed `target` (`tabId`, `frameIds`/`allFrames`, or `documentIds` when available) and `world`/`injectImmediately` options.\n  - Firefox does not support `documentIds` — the library will automatically avoid using them.\n\n## Recipes\n\n- Inject as early as possible:\n  - Set `runAt: 'document_start'` (MV2); in MV3 this enables `injectImmediately: true`.\n- Inject into an isolated world (MV3):\n  - `world: 'ISOLATED'` — your code won’t conflict with the page script.\n- Performance:\n  - Group files in a single `file([..])` call when possible to reduce overhead.\n- Safety:\n  - Functions passed to `run` should be self-contained: rely only on what’s available in the page context. In MV2 they are string-serialized.\n\n## Troubleshooting\n\n- Timeout error (MV2): increase `timeFallback`.\n- `undefined` result (MV2): check the page console — the function may have thrown.\n- Nothing happens:\n  - Ensure your extension has permissions for the target tab/frame(s).\n  - Verify `tabId`, `frameId`/`documentId`, and the `runAt` timing.\n- Conflicts with page code (MV3): use `world: 'ISOLATED'`.\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faddon-stack%2Finject-script","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faddon-stack%2Finject-script","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faddon-stack%2Finject-script/lists"}