{"id":18369816,"url":"https://github.com/cawfree/react-native-wormhole","last_synced_at":"2025-04-05T12:06:09.479Z","repository":{"id":53740476,"uuid":"346854112","full_name":"cawfree/react-native-wormhole","owner":"cawfree","description":"⚛️ 🌌  Inter-dimensional Portals for React Native. 👽 🖖","archived":false,"fork":false,"pushed_at":"2021-03-16T18:50:48.000Z","size":775,"stargazers_count":319,"open_issues_count":0,"forks_count":14,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-03-29T11:09:30.704Z","etag":null,"topics":["component","dynamic","external","loader","plugin","react-native","render","uri"],"latest_commit_sha":null,"homepage":"https://twitter.com/cawfree","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/cawfree.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-03-11T22:16:14.000Z","updated_at":"2025-01-23T15:04:38.000Z","dependencies_parsed_at":"2022-09-16T09:20:55.752Z","dependency_job_id":null,"html_url":"https://github.com/cawfree/react-native-wormhole","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cawfree%2Freact-native-wormhole","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cawfree%2Freact-native-wormhole/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cawfree%2Freact-native-wormhole/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cawfree%2Freact-native-wormhole/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cawfree","download_url":"https://codeload.github.com/cawfree/react-native-wormhole/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246756713,"owners_count":20828751,"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":["component","dynamic","external","loader","plugin","react-native","render","uri"],"created_at":"2024-11-05T23:32:20.701Z","updated_at":"2025-04-05T12:06:09.447Z","avatar_url":"https://github.com/cawfree.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🌌 [`react-native-wormhole`](http://npmjs.com/package/react-native-wormhole)\nA `Wormhole` allows your [⚛️ **React Native**](https://reactnative.dev) application to consume components from a remote URL as if it were a local `import`, enabling them to easily become remotely configurable at runtime!\n\n[🎬 **Watch the Demo!**](https://twitter.com/cawfree/status/1370809787294879746)\n\n\u003e ⚠️ Implementors must take care to protect their Wormholes from **arbitrary code execution**. Insufficient protection will put your user's data and device at risk. 💀 Please see [**Verification and Signing**](https://github.com/cawfree/react-native-wormhole#-verification-and-signing) for more information.\n\n### 🚀 Getting Started\n\nUsing [**Yarn**](https://yarnpkg.com):\n\n```sh\nyarn add react-native-wormhole\n```\n\nNext, you'll need a component to serve. Let's create a quick project to demonstrate how this works:\n\n```\nmkdir my-new-wormhole\ncd my-new-wormhole\nyarn init\nyarn add --dev @babel/core @babel/cli @babel/preset-env @babel/preset-react\n```\n\nThat should be enough. Inside `my-new-wormhole/`, let's quickly create a simple component:\n\n**`my-new-wormhole/MyNewWormhole.jsx`**:\n\n```javascript\nimport * as React from 'react';\nimport { Animated, Alert, TouchableOpacity } from 'react-native';\n\nfunction CustomButton() {\n  return (\n    \u003cTouchableOpacity onPress={() =\u003e Alert.alert('Hello!')}\u003e\n      \u003cAnimated.Text children=\"Click here!\" /\u003e\n    \u003c/TouchableOpacity\u003e\n  );\n}\n\nexport default function MyNewWormhole() {\n  const message = React.useMemo(() =\u003e 'Hello, world!', []);\n  return (\n    \u003cAnimated.View style={{ flex: 1, backgroundColor: 'red' }}\u003e\n      \u003cAnimated.Text\u003e{message}\u003c/Animated.Text\u003e\n      \u003cCustomButton /\u003e\n    \u003c/Animated.View\u003e\n  );\n}\n```\n\n\u003e 🤔 **What syntax am I allowed to use?**\n\u003e \n\u003e By default, you can use all functionality exported by `react` and `react-native`. The only requirement is that you must `export default` the Component that you wish to have served through the `Wormhole`.\n\nNow our component needs to be [**transpiled**](https://babeljs.io/docs/en/babel-cli). Below, we use [**Babel**](https://babeljs.io/) to convert `MyNewWormhole` into a format that can be executed at runtime:\n\n```\nnpx babel --presets=@babel/preset-env,@babel/preset-react MyNewWormhole.jsx -o MyNewWormhole.js\n```\n\nAfter doing this, we'll have produced `MyNewWormhole.js`, which has been expressed in a format that is suitable to serve remotely. If you're unfamiliar with this process, take a quick look through the contents of the generated file to understand how it has changed.\n\nNext, you'd need to serve this file somewhere. For example, you could save it on GitHub, [**IPFS**](https://ipfs.io/) or on your own local server. To see an example of this, check out the [**Example Server**](./example/scripts/serve.js).\n\n\u003e 👮 **Security Notice**\n\u003e \n\u003e In production environments, you **must** serve content using [**HTTPS**](https://en.wikipedia.org/wiki/HTTPS) to prevent [**Man in the Middle**](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) attacks. Additionally, served content must be signed using public-key encryption to ensure authenticity of the returned source code. A demonstration of this approach using [**Ethers**](https://github.com/ethers-io/ethers.js/) is shown in the [**Example App**](https://github.com/cawfree/react-native-wormhole/blob/bdb127b21e403dab1fb63894f5d6764a92a002d4/example/App.tsx#L11).\n\n\nFinally, let's render our `\u003cApp /\u003e`! For the purpose of this tutorial, let's assume the file is served at [https://cawfree.com/MyNewWormhole.jsx](https://cawfree.com/MyNewWormhole.jsx):\n\n```javascript\nimport * as React from 'react';\nimport { createWormhole } from 'react-native-wormhole';\n\nconst { Wormhole } = createWormhole({\n  verify: async () =\u003e true,\n});\n\nexport default function App() {\n  return \u003cWormhole source={{ uri: 'https://cawfree.com/MyNewWormhole.jsx' }} /\u003e;\n}\n```\n\nAnd that's everything! Once our component has finished downloading, it'll be mounted and visible on screen. 🚀\n\n### 🔩 Configuration\n\n#### 🌎 Global Scope\n\nBy default, a `Wormhole` is only capable of consuming global functionality from two different modules; [`react`](https://github.com/facebook/react) and [`react-native`](https://github.com/facebook/react-native), meaning that only \"vanilla\" React Native functionality is available. However, it is possible to introduce support for additional modules. In the snippet below, we show how to allow a `Wormhole` to render a [`WebView`](https://github.com/react-native-webview/react-native-webview):\n\n```diff\nconst { Wormhole } = createWormhole({\n+  global: {\n+    require: (moduleId: string) =\u003e {\n+      if (moduleId === 'react') {\n+        return require('react');\n+      } else if (moduleId === 'react-native') {\n+        return require('react-native');\n+      } else if (moduleId === 'react-native-webview') {\n+        return require('react-native-webview);\n+      }\n+      return null;\n+    },\n+  },\n  verify: async () =\u003e true,\n});\n```\n\n\u003e ⚠️  Version changes to `react`, `react-native` or any other dependencies your Wormholes consume may not be backwards-compatible. It's recommended that APIs serving content to requestors verify the compatibility of the requester version to avoid serving incompatible content. `react-native-wormhole` is **not** a package manager!\n\n#### 🔏 Verification and Signing\n\nCalls to [`createWormhole`](./src/constants/createWormhole.tsx) must at a minimum provide a `verify` function, which has the following declaration:\n\n```typescript\nreadonly verify: (response: AxiosResponse\u003cstring\u003e) =\u003e Promise\u003cboolean\u003e;\n```\n\nThis property is used to determine the integrity of a response, and is responsible for identifying whether remote content may be trusted for execution. If the `async` function does not return `true`, the request is terminated and the content will not be rendered via a `Wormhole`. In the [**Example App**](https://github.com/cawfree/react-native-wormhole/blob/bdb127b21e403dab1fb63894f5d6764a92a002d4/example/App.tsx#L11), we show how content can be signed to determine the authenticity of a response:\n\n```diff\n+ import { ethers } from 'ethers';\n+ import { SIGNER_ADDRESS, PORT } from '@env';\n\nconst { Wormhole } = createWormhole({\n+  verify: async ({ headers, data }: AxiosResponse) =\u003e {\n+    const signature = headers['x-csrf-token'];\n+    const bytes = ethers.utils.arrayify(signature);\n+    const hash = ethers.utils.hashMessage(data);\n+    const address = await ethers.utils.recoverAddress(\n+      hash,\n+      bytes\n+    );\n+    return address === SIGNER_ADDRESS;\n+  },\n});\n```\n\nIn this implementation, the server is expected to return a HTTP response header `x-csrf-token` whose value is a [`signedMessage`](https://docs.ethers.io/v5/api/signer/) of the response body. Here, the client computes the expected signing address of the served content using the digest stored in the header.\n\nIf the recovered address is not trusted, the script **will not be executed**.\n\n#### 🏎️  Preloading\n\nMaking a call to `createWormhole()` also returns a `preload` function which can be used to asynchronously cache remote JSX before a `Wormhole` has been mounted:\n\n```typescript\nconst { preload } = createWormhole({ verify: async () =\u003e true });\n\n(async () =\u003e {\n  try {\n    await preload('https://cawfree.com/MyNewWormhole.jsx');\n  } catch (e) {\n    console.error('Failed to preload.');\n  }\n})();\n```\n\nWormholes dependent upon the external content will subsequently render immediately if the operation has completed in time. Meanwhile, concurrent requests to the same resource will be deduped.\n\n### ✌️ License\n[**MIT**](./LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcawfree%2Freact-native-wormhole","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcawfree%2Freact-native-wormhole","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcawfree%2Freact-native-wormhole/lists"}