{"id":23112310,"url":"https://github.com/alshdavid-labs/rpc","last_synced_at":"2025-08-23T15:10:30.174Z","repository":{"id":46285712,"uuid":"324480483","full_name":"alshdavid-labs/rpc","owner":"alshdavid-labs","description":"Web Worker communication tools","archived":false,"fork":false,"pushed_at":"2021-11-03T04:10:54.000Z","size":539,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-04-03T23:45:04.641Z","etag":null,"topics":["javascript","typescript","web-worker","web-workers","webworker","webworkers"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alshdavid-labs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-12-26T04:24:03.000Z","updated_at":"2022-11-04T08:06:14.000Z","dependencies_parsed_at":"2022-08-28T14:10:09.414Z","dependency_job_id":null,"html_url":"https://github.com/alshdavid-labs/rpc","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/alshdavid-labs/rpc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alshdavid-labs%2Frpc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alshdavid-labs%2Frpc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alshdavid-labs%2Frpc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alshdavid-labs%2Frpc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alshdavid-labs","download_url":"https://codeload.github.com/alshdavid-labs/rpc/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alshdavid-labs%2Frpc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271754367,"owners_count":24815181,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-23T02:00:09.327Z","response_time":69,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["javascript","typescript","web-worker","web-workers","webworker","webworkers"],"created_at":"2024-12-17T02:17:41.784Z","updated_at":"2025-08-23T15:10:30.129Z","avatar_url":"https://github.com/alshdavid-labs.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JavaScript RPC Library\n\n\u003cb\u003eGlue for Web Workers and iFrames\u003c/b\u003e\n\nThis library serves to be a tiny (3kb), low level RPC implementation for JavaScript that allows for the interaction of values that exist in an external context. Think iframes and web workers.\n\n\u003cp align=\"center\"\u003e\n  \u003cbr\u003e\n  \u003cimg src=\".github/hero.png\" /\u003e\n  \u003ci\u003eAccesing something on the host page from a Worker\u003cbr\u003eCan also work the other way around\u003c/i\u003e\n  \u003cbr\u003e\n\u003c/p\u003e\n\n## Sample Projects\n\nFor a practical example of how this works, sample projects are available in the [`sample-projects`](./sample-projects) folder.\n\nThese projects are also available for viewing hosted here:\n\n- [iframe example](https://cdn.davidalsh.com/rpc/sample-projects/iframes/index.html)\n- [web worker example](https://cdn.davidalsh.com/rpc/sample-projects/web-worker/index.html)\n\n\n## Installation\n\n### NPM\n\nPretty standard npm installation\n\n```shell\nnpm install --save @alshdavid/rpc\n```\n\nThen import using ES Modules or CommonJS Modules \u003ci\u003e(Node 14+ allows for differential loading)\u003c/i\u003e\n```typescript\nimport { DataSource, Reference } from '@alshdavid/rpc'\nconst { DataSource, Reference } = require('@alshdavid/rpc')\n```\n\n### CDN\n\nYou can add this directly as a `\u003cscript\u003e` tag on your page with:\n\n```html\n\u003cscript src=\"https://cdn.davidalsh.com/rpc/latest.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n  const { DataSource, Reference } = RPC\n\u003c/script\u003e\n```\n\nFrom Modules/Module Web Workers you can import the URL:\n\n```javascript\nimport { DataSource, Reference } from 'https://cdn.davidalsh.com/rpc/latest.js'\n```\n\nFor Web Workers that are not using modules you can use\n\n```javascript\nimportScripts('https://cdn.davidalsh.com/rpc/latest.js')\nconst { DataSource, Reference } = RPC\n```\n\nAlternatively you can specify the version of the library in the URL:\n```html\n\u003cscript src=\"https://cdn.davidalsh.com/rpc/latest.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://cdn.davidalsh.com/rpc/2.0.1.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://cdn.davidalsh.com/rpc/next.js\"\u003e\u003c/script\u003e\n```\n\n## Library API\n\nThis is split into two halves: \n\nThe `DataSource` is the entity that has the target data in it's native context and the `Reference` is the entity that is consuming and interacting with that data.\n\n### Data Source\n\nThe `DataSource` simply exposes a variable. It can be an Object, string, function, etc.\n\n```typescript\n// Swap MessagePorts\nconst source = new DataSource(port1, { Expose: 'This' })\n```\n\n### Reference\n\nThe `Reference` acts like a cursor pointer to a segment of the data stored within the remote `DataSource`. It begins at the root level and can traverse the source.\n\n```typescript\n// Swap MessagePorts\nconst ref0 = new Reference(port2)\nconst ref1 = ref0.property('Expose')\nconst value = await ref1.value() // 'This'\n```\n\n#### Methods\n\nThe `Reference` interacts with the source data using methods that observe, call or move the cursor. \n\n```typescript\ninterface IReference {\n  property(...pathSegments: string[]): IReference\n  set(value: any): Promise\u003cvoid\u003e\n  value(): Promise\u003cunknown\u003e\n  exec(...args: any[]): Promise\u003cIReference\u003e\n  release(): Promise\u003cvoid\u003e\n}\n```\n\nDifferent data types are eligible for different methods, for example you can not run `exec()` on a `string` as it's not a callable type, but you can use it on a function or method.\n\n|method|usage|\n|-|-|\n|`property`|This is used to traverse an object, it returns a new reference pointer to the path specified. \u003cbr\u003e\u003cbr\u003eThe originating reference pointer is kept, you can have multiple references. \u003cbr\u003e\u003cbr\u003eCertain calls will result in cached values and those references must be manually released when done with them.\n|`set`|This is used to set a property on an object to a certain value|\n|`value`|This method will convert a `Reference` into the value held at that reference location. \u003cbr\u003e\u003cbr\u003eIt's important to remember that only serializable types can be transferred like this.\u003cbr\u003e\u003cbr\u003eFortunately, not all values need to be transferred. Remote function invocation can accept `Reference` types which the source will convert into the native values|\n|`exec`|This method will invoke a method or function at the current `Reference` path.\u003cbr\u003e\u003cbr\u003eIt accepts simple serializable arguments like `string`, it accepts callback functions and `Reference` types as arguments.\u003cbr\u003e\u003cbr\u003eIt will return a `Reference` to it's return value.|\n|`release`|This is used to purge the local and remote caches of values relevant to the current `Reference` cursor.\u003cbr\u003e\u003cbr\u003eFailing to do this will result in memory leaks in both the remote and local contexts.|\n\n## Use Cases\n\n### Web Workers\n\nA Web Worker may want access to the Window object on the host page. To do this, the host page will create a `DataSource` exposing the `window` object, then the worker will create a `Reference` to that, interacting with it as required.\n\nAlternatively, if a Web Worker contains capabilities that the host page would like to interact with, then the Worker can create a `DataSource`, exposing the desired vales to the host page.\n\nIt's also possible for the host page and the Worker to both be consumers and data sources, cross exporting things on either side.\n\n### iFrames\n\nSimilarly to Web Workers, cross origin iframes do not have access to the host page's Window object.\n\nUsing this library, it is possible to either export functionality from an iframe, consume functionality from a host page or both simultaneously.\n\n### Catches\n\nIt's important to acknowledge that both entities (worker, iframe, host page) must have the RPC library installed and explicitly expose/consume capabilities from the other.\n\nThis does not give unlimited access to the `DataSource` context, only limited remote execution capabilities on an explicitly exported value.\n\n## Callback function arguments\n\nFunctions that accepts functions as arguments are supported by this library.\n_So yes, you can use function over a serialized boundary._\n\nBelow are some examples with their remote implementations described\n\n```typescript\n// const ref0 = () =\u003e 'Hello World'\n\nconst resultRef = await ref0.exec()\nconst value = await resultRef.value()\n\nconsole.log(value) // 'Hello World'\n```\n\n```typescript\n// const ref0 = (value) =\u003e value\n\nconst resultRef = await ref0.exec('Hello World')\nconst value = await resultRef.value()\n\nconsole.log(value) // 'Hello World'\n```\n\n```typescript\n// const ref0 = (callback) =\u003e callback()\n\nawait ref0.exec(() =\u003e {\n  console.log('callback')\n})\n```\n\nFunction arguments that supply function arguments to the callback are also supported.\n```typescript\n// const nextFunc = () =\u003e {}\n// const ref0 = (callback) =\u003e callback(nextFunc)\n\nawait ref0.exec(async nextRef =\u003e {\n  await nextRef.exec()\n})\n```\n\n#### Exceptions\n\nErrors thrown in the data source are propagated to the consumer through the reference. The errors are references to the error in the data source.\n\n```typescript\ntry {\n  await ref0.exec()\n} catch (errorRef) {\n  const remoteMessage = await errorRef.reference.property('message').value()\n  console.log(remoteMessage)\n}\n```\n\n#### Realized values vs cursor traversal\n\nValues are only transferred when `.value()`, `.set()` and (when arguments are supplied) `.exec()` are called. \n\nCalling `.property()` or `.release()` will not transfer any values.\n\nGenerally, values are interacted with as references and are only transferred when the consumer requires the value within its own context. If an attempt to transfer a type that is not serializable is made, it will fail to send. \n\nThe library will automatically convert/send callbacks to the data source as references when passed into `.exec()`, but more complex types like objects with functions will not work (at least not yet). It may be required to write some small adapters/shims to facilitate smooth API access.\n\n```typescript\n// Data = { foo: { bar: { foobar: 'foobar' }}}\n\nconst ref1 = await ref0.property('foo') // Create reference with path\nconst ref2 = await ref1.property('bar', 'foobar') // Build it up\n\nconst foobar = await ref2.value() // Now get the value there \n```\n\n#### Memory Management\n\nThere might be some improvements I can make here with time, but for now it's important to `.release()` references that are cached in the `DataSource`.\n\nThe types of values that are cached include:\n- function return values \n- callback functions\n- callback parameters\n\nThese are associated with their `Reference` and must be manually purged when no longer needed to avoid memory leaks.\n\n```typescript\n// const ref0 = () =\u003e 'Hello World'\n\nconst returnValueRef = await ref0.exec()\n\n// The reference inside the DataSource for the return value\n// is associated with the returned Reference on the consumer\n// The consumer may use this value for as long as the value\n// remains in the data source\nconst helloWorld = await returnValueRef.value()\nconsole.log(helloWorld) // 'Hello World'\n\n// Once the consumer no longer needs the value they are \n// required to manually purge values associated with their\n// Reference by calling the following method\nawait returnValueRef.release()\n\n// Subsequent attempts to access a released value will\n// result in an undefined value being returned\nconst secondHelloWorld = await returnValueRef.value()\nconsole.log(secondHelloWorld) // undefined\n```\n\n```typescript\n// const fooFn = () =\u003e {}\n// const barFn = () =\u003e {}\n// const ref0 = (callback) =\u003e callback(fooFn, barFn)\n\nconst returnValueRef = await ref0.exec(async (fooRef, barRef) =\u003e {\n  // In situations where values are provided as callback arguments\n  // the values must be released from within the callback\n  console.log(await fooRef.value()) // 'foo'\n  console.log(await barRef.value()) // 'bar'\n\n  await fooRef.release()\n  await barRef.release()\n})\n\n// The callback also needs to be released\n// This may be changed in the future\nref0.release()\n```\n\n### MessagePorts\n\nThe base API uses the browser `MessagePort` as the communication interface, so transfer your ports between your entities and you're good to go.\nIt's usually best to send the port from the `iframe` to the host page rather than the other way around.\n\n```typescript\nimport { Reference } from '@alshdavid/rpc'\n\nconst iframe = document.createElement('iframe')\niframe.src = 'https://external-origin.com/iframe.html'\n\nconst onPort2 = new Promise(\n  res =\u003e addEventListener('message', \n    e =\u003e e.data === 'PORT_TRANSFER' \u0026\u0026 res(e.ports[0])))\n\ndocument.body.appendChild(iframe)\nconst port2 = await onPort2\nport2.start()\n\nconst source = new Reference(port2, window)\n```\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falshdavid-labs%2Frpc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falshdavid-labs%2Frpc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falshdavid-labs%2Frpc/lists"}