{"id":13566944,"url":"https://github.com/hazae41/dstorage","last_synced_at":"2025-06-15T19:10:03.976Z","repository":{"id":240891174,"uuid":"803700217","full_name":"hazae41/dstorage","owner":"hazae41","description":"Origin-agnostic storage for your dapp","archived":false,"fork":false,"pushed_at":"2024-08-04T15:22:47.000Z","size":561,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-09T10:17:30.826Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://dstorage.hazae41.me","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/hazae41.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-05-21T08:17:04.000Z","updated_at":"2024-08-04T15:21:09.000Z","dependencies_parsed_at":"2024-05-21T10:03:47.321Z","dependency_job_id":"132e60ff-a5e5-4b0a-9cb4-a207119d450f","html_url":"https://github.com/hazae41/dstorage","commit_stats":null,"previous_names":["hazae41/dstorage"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/hazae41/dstorage","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hazae41%2Fdstorage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hazae41%2Fdstorage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hazae41%2Fdstorage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hazae41%2Fdstorage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hazae41","download_url":"https://codeload.github.com/hazae41/dstorage/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hazae41%2Fdstorage/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260036065,"owners_count":22949255,"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","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":[],"created_at":"2024-08-01T13:02:20.058Z","updated_at":"2025-06-15T19:10:03.930Z","avatar_url":"https://github.com/hazae41.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# DStorage\n\nProof-of-concept of a public and secure origin-agnostic storage for your origin-less apps.\n\n## Why\n\nThis allows your website to use the storage (KeyValue and WebAuthn) of another origin.\n\nThis is particularily useful when your website doesn't have a single origin.\n\nFor example, if you use the same app from multiple origins (e.g. browser extensions, mobile webviews).\n\nOr if you want maximum decentralization by allowing multiple origins to operate on the same storage (e.g. IPFS websites).\n\nFor example, allowing both https://myapp.ipfs.io and https://myapp.example.com to have the same storage.\n\nOr you just want some basic versioning like https://1.myapp.org and https://2.myapp.org.\n\n## Communication\n\nA few modes of cross-origin communication are available depending on your constraints.\n\n### Service-worker to service-worker communication (page-bootstrapped)\n\nYou just need to open a server page to bootstrap the communication from your service-worker to the server service-worker.\n\nYou must keep one server page open in order to keep the server service-worker running.\n\nThis communication continues to work even a few seconds after all pages are closed (~30 seconds on Chromium).\n\nWhen the communication is closed, just reopen a new bootstrap page.\n\n### Page to service-worker communication (page-bootstrapped)\n\nThis is the same as above but this time the communication is closed once you close the client page.\n\n### Service-worker to service-worker communication (iframe-bootstrapped)\n\nOnly available on Chrome with `document.requestStorageAccess({ caches: true })`. \n\nYou use an iframe to bootstrap the communication and maintain the server service-worker running.\n\nNo page is opened except when asking for user interaction.\n\n### Page to service-worker communication (iframe-bootstrapped)\n\nThis is the same as above but this time the communication is closed once you close the client page.\n\n## APIs\n\nAll communication is done via JSON-RPC 2.0.\n\n### Example\n\nGiven the function\n\n```tsx\nfunction example(value: boolean): void\n```\n\nYou can call it with\n\n```tsx\nport.postMessage([{ jsonrpc: \"2.0\", id: 123, method: \"example\", params: [true] }])\n```\n\nAnd get the result with\n\n```tsx\nconst [{ id, result, error }] = event.data\n```\n\n### Cache\n\nThis is a key-value storage using Cache API.\n\nThe access requires user-interaction\n\n```tsx\nfunction kv_ask(scope: string, capacity: number): void\n```\n\nThis will ask user-interaction for access to `scope` and grow it to `capacity` bytes\n\n```tsx\nfunction kv_set(scope: string, request: RequestLike, response: ResponseLike): void\n```\n\nThis will set `key` to `value` in `scope`\n\n```tsx\nfunction kv_get(scope: string, request: RequestLike): unknown\n```\n\nThis will return `value` from `key` in `scope`\n\n(`RequestLike` and `ResponseLike` are just JSON-compatible versions of `Request` and `Response`)\n\n### WebAuthn\n\nThis is a remote version of [WebAuthnStorage](https://github.com/hazae41/webauthnstorage).\n\nThis will open a page requiring user confirmation.\n\n```tsx\nfunction webauthn_storage_create(name: string, data: Uint8Array): Uint8Array\n```\n\n```tsx\nfunction webauthn_storage_get(handle: Uint8Array): Uint8Array\n```\n\n## Limitations\n\nThe main limitation is storage availability. \n\nOn some browsers the storage is not persistent unless the user adds the storage website to his favorites.\n\nAlso, as explained below, an attacker can try to fill the storage with random stuff until it's full.\n\nThis can be mitigated by only allowing a limited storage from user-approved origins using a confirmation page.\n\nThis can make the UX worse than just using a local storage, but can prevent phishing because a new origin will show a warning.\n\n## Security\n\nSince all operation are cross-origin through JSON-like messages, there is no much security risk as the browser sandboxes everything.\n\n### Attacker from a third-party website\n\nAll storages do not have discoverability, so there is no risk of an attacker seeing or tampering with the data. \n\nHe would need to know the storage keys of the data.\n\nBruteforce is not viable if you use strong random-like keys like UUIDs or hashes.\n\nEven if he can guess the keys, assuming your data is strongly encrypted, he would only be able to delete that data.\n\nAnother possible attack is filling the storage with random stuff to cause the storage to be full.\n\nThis can be mitigated by the storage website; only allowing a limited storage from user-approved origins.\n\n### Attacker from the storage website\n\nIf the storage website is compromised (supply-chain, DNS, BGP attack), there is not much it can do.\n\nAssuming your data is strongly encrypted, it can't do anything beside deleting that data or refusing to serve it.\n\n### Attacker from your website\n\nIf your own website is compromised then the storage is probably available to the attacker, just like any local storage.\n\nYou can somewhat mitigate this by encrypting your data using an user-provided password or some WebAuthn authentication.\n\n## Protocols\n\n### Explicit Transfer Protocol (ETP)\n\nAll messages sent via `postMessage` are in the format `[message: unknown, transferreds: Transferable[]]`\n\ne.g. \n\n```tsx\nconst bytes = new Uint8Array([1,2,3,4,5])\n\nwindow.postMessage([{\n  method: \"example\",\n  params: [bytes]\n}, [bytes]], [bytes])\n```\n\nThis allows explicit passing of transferable objects and thus zero-copy messaging and proxying\n\ne.g.\n\n```tsx\n/**\n * Proxy and transfer from port to port2\n */\nport.onmessage = (e) =\u003e {\n  const [m, t] = e.data\n\n  /**\n   * t are transferred again\n   */\n  port2.postMessage([m, t], t)\n}\n```\n\n### D-JSON-RPC (UDP-like multicast-like communication)\n\nThis is used via `postMessage` by pages (and iframes) and service-workers before a `MessagePort` is shared\n\n#### Format\n\nThis is like JSON-RPC but there is no request-response\n\nThere are only JSON messages using the following format\n\n```tsx\n{\n  method: string,\n  params: unknown\n}\n```\n\n#### ping\n\nPerform a ping to ensure the target is available for a `connect` or `connect2`\n\nOnly used for page targets as for service-worker targets we can use `serviceWorker.ready` heuristic\n\n```tsx\n{\n  method: \"ping\"\n}\n```\n\nThe target is available if a `pong` is received\n\n- Why use `ping` when there is already `hello`?\n\nA `ping` can be sent from/to a middlebox whereas `hello` is always sent end-to-end \n\n- Why don't use a `connected` or `connected2` reply?\n\nBecause all communication here is multicast-like so a `connected` may trigger others\n\n#### pong\n\nReply to a `ping`\n\n```tsx\n{\n  method: \"pong\"\n}\n```\n\n#### connect\n\nEstablish an end-to-end communication to the target\n\n```tsx\n{\n  method: \"connect\"\n}\n```\n\nThe message also contains a `MessagePort` to use as a bidirectional port\n\n#### connect2\n\nEstablish an end-to-end communication to the target's service-worker\n\n```tsx\n{\n  method: \"connect2\"\n}\n```\n\nThe message also contains a `MessagePort` to use as a bidirectional port\n\n#### connect3\n\nSent by a page to its service-worker after a `connect2`\n\n```tsx\n{\n  method: \"connect3\",\n  params: [trueOrigin]\n}\n```\n\nThe message also contains a `MessagePort` to use as a bidirectional port\n\n### JSON-RPC (TCP-like unicast-like communication)\n\nOnce a end-to-end communication is established via `MessagePort`\n\n#### hello\n\nPerform a fast bidirectional active-passive handshake to ensure the target is available\n\n```tsx\n{\n  method: \"hello\"\n}\n```\n\nThe connection is ready when both sides received either a `hello` request or a `hello` response","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhazae41%2Fdstorage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhazae41%2Fdstorage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhazae41%2Fdstorage/lists"}