diff --git a/examples/helia-service-worker-gateway/.github/pull_request_template.md b/examples/helia-service-worker-gateway/.github/pull_request_template.md
new file mode 100644
index 00000000..04bd7d52
--- /dev/null
+++ b/examples/helia-service-worker-gateway/.github/pull_request_template.md
@@ -0,0 +1,17 @@
+# ⚠️ IMPORTANT ⚠️
+
+# Please do not create a Pull Request for this repository
+
+The contents of this repository are automatically synced from the parent [Helia Examples Project](https://github.com/ipfs-examples/helia-examples) so any changes made to the standalone repository will be lost after the next sync.
+
+Please open a PR against [IPFS Examples](https://github.com/ipfs-examples/helia-examples) instead.
+
+## Contributing
+
+Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**.
+
+1. Fork the [Helia Examples Project](https://github.com/ipfs-examples/helia-examples)
+2. Create your Feature Branch (`git checkout -b feature/amazing-example`)
+3. Commit your Changes (`git commit -a -m 'feat: add some amazing example'`)
+4. Push to the Branch (`git push origin feature/amazing-example`)
+5. Open a Pull Request
diff --git a/examples/helia-service-worker-gateway/.github/workflows/sync.yml b/examples/helia-service-worker-gateway/.github/workflows/sync.yml
new file mode 100644
index 00000000..3f8b7446
--- /dev/null
+++ b/examples/helia-service-worker-gateway/.github/workflows/sync.yml
@@ -0,0 +1,19 @@
+name: pull
+
+on:
+ workflow_dispatch
+
+jobs:
+ sync:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Pull from another repository
+ uses: ipfs-examples/actions-pull-directory-from-repo@main
+ with:
+ source-repo: ipfs-examples/helia-examples
+ source-folder-path: examples/${{ github.event.repository.name }}
+ source-branch: main
+ target-branch: main
+ git-username: github-actions
+ git-email: github-actions@github.com
diff --git a/examples/helia-service-worker-gateway/README.md b/examples/helia-service-worker-gateway/README.md
new file mode 100644
index 00000000..0770ef9d
--- /dev/null
+++ b/examples/helia-service-worker-gateway/README.md
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+Bundle Helia with Webpack
+
+
+
+
+ Explore the docs
+ ·
+ View Demo
+ ·
+ Report Bug
+ ·
+ Request Feature/Example
+
+
+## Table of Contents
+
+- [Table of Contents](#table-of-contents)
+- [About The Project](#about-the-project)
+- [Getting Started](#getting-started)
+ - [Prerequisites](#prerequisites)
+ - [Installation and Running example](#installation-and-running-example)
+- [Usage](#usage)
+- [Documentation](#documentation)
+- [Contributing](#contributing)
+- [Want to hack on IPFS?](#want-to-hack-on-ipfs)
+
+## About The Project
+
+- Read the [docs](https://ipfs.github.io/helia/modules/helia.html)
+- Look into other [examples](https://github.com/ipfs-examples/helia-examples) to learn how to spawn a Helia node in Node.js and in the Browser
+- Visit https://dweb-primer.ipfs.io to learn about IPFS and the concepts that underpin it
+- Head over to https://proto.school to take interactive tutorials that cover core IPFS APIs
+- Check out https://docs.ipfs.io for tips, how-tos and more
+- See https://blog.ipfs.io for news and more
+- Need help? Please ask 'How do I?' questions on https://discuss.ipfs.io
+
+## Getting Started
+
+### Prerequisites
+
+Make sure you have installed all of the following prerequisites on your development machine:
+
+- Git - [Download & Install Git](https://git-scm.com/downloads). OSX and Linux machines typically have this already installed.
+- Node.js - [Download & Install Node.js](https://nodejs.org/en/download/) and the npm package manager.
+
+### Installation and Running example
+
+```console
+> npm install
+> npm start
+```
+
+Now open your browser at `http://localhost:3000`
+
+## Usage
+
+In this example, you will find a boilerplate you can use to guide yourself into bundling helia with webpack, so that you can use it in your own web app!
+
+You should see the following:
+
+
+
+_For more examples, please refer to the [Documentation](#documentation)_
+
+## Documentation
+
+- [IPFS Primer](https://dweb-primer.ipfs.io/)
+- [IPFS Docs](https://docs.ipfs.io/)
+- [Tutorials](https://proto.school)
+- [More examples](https://github.com/ipfs-examples/helia-examples)
+- [API - Helia](https://ipfs.github.io/helia/modules/helia.html)
+- [API - @helia/unixfs](https://ipfs.github.io/helia-unixfs/modules/helia.html)
+
+## Contributing
+
+Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**.
+
+1. Fork the IPFS Project
+2. Create your Feature Branch (`git checkout -b feature/amazing-feature`)
+3. Commit your Changes (`git commit -a -m 'feat: add some amazing feature'`)
+4. Push to the Branch (`git push origin feature/amazing-feature`)
+5. Open a Pull Request
+
+## Want to hack on IPFS?
+
+[](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)
+
+The IPFS implementation in JavaScript needs your help! There are a few things you can do right now to help out:
+
+Read the [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md) and [JavaScript Contributing Guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md).
+
+- **Check out existing issues** The [issue list](https://github.com/ipfs/helia/issues) has many that are marked as ['help wanted'](https://github.com/ipfs/helia/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22help+wanted%22) or ['difficulty:easy'](https://github.com/ipfs/helia/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Adifficulty%3Aeasy) which make great starting points for development, many of which can be tackled with no prior IPFS knowledge
+- **Look at the [Helia Roadmap](https://github.com/ipfs/helia/blob/main/ROADMAP.md)** This are the high priority items being worked on right now
+- **Perform code reviews** More eyes will help
+ a. speed the project along
+ b. ensure quality, and
+ c. reduce possible future bugs
+- **Add tests**. There can never be enough tests
+
+[cid]: https://docs.ipfs.tech/concepts/content-addressing "Content Identifier"
+[Uint8Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array
+[libp2p]: https://libp2p.io
diff --git a/examples/helia-service-worker-gateway/img/1.png b/examples/helia-service-worker-gateway/img/1.png
new file mode 100644
index 00000000..e7dd1bbc
Binary files /dev/null and b/examples/helia-service-worker-gateway/img/1.png differ
diff --git a/examples/helia-service-worker-gateway/package.json b/examples/helia-service-worker-gateway/package.json
new file mode 100644
index 00000000..0e44cd77
--- /dev/null
+++ b/examples/helia-service-worker-gateway/package.json
@@ -0,0 +1,56 @@
+{
+ "name": "helia-service-worker-gateway",
+ "version": "1.0.0",
+ "private": true,
+ "type": "module",
+ "description": "Bundle Helia with Webpack",
+ "keywords": [],
+ "license": "MIT",
+ "scripts": {
+ "clean": "rimraf ./dist",
+ "build": "webpack --env production",
+ "serve": "webpack serve --mode=development",
+ "start": "npm run serve",
+ "test": "npm run build && playwright test tests"
+ },
+ "browserslist": [
+ "last 1 Chrome version"
+ ],
+ "dependencies": {
+ "@chainsafe/libp2p-noise": "^11.0.0",
+ "@chainsafe/libp2p-yamux": "^3.0.5",
+ "@helia/unixfs": "^1.0.2",
+ "@libp2p/bootstrap": "^6.0.0",
+ "@libp2p/delegated-content-routing": "^4.0.1",
+ "@libp2p/delegated-peer-routing": "^4.0.1",
+ "@libp2p/websockets": "^5.0.3",
+ "@libp2p/webtransport": "^1.0.7",
+ "blockstore-core": "^3.0.0",
+ "datastore-core": "^8.0.4",
+ "helia": "next",
+ "kubo-rpc-client": "^3.0.1",
+ "libp2p": "^0.42.2",
+ "multiformats": "^11.0.2",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.13.10",
+ "@babel/preset-env": "^7.13.12",
+ "@babel/preset-react": "^7.12.13",
+ "@playwright/test": "^1.12.3",
+ "babel-loader": "^9.1.2",
+ "copy-webpack-plugin": "^11.0.0",
+ "css-loader": "^6.2.0",
+ "html-webpack-plugin": "^5.3.2",
+ "node-polyfill-webpack-plugin": "^2.0.1",
+ "playwright": "^1.12.3",
+ "rimraf": "^4.1.2",
+ "style-loader": "^3.1.0",
+ "test-util-ipfs-example": "^1.0.2",
+ "webpack": "^5.45.1",
+ "webpack-cli": "^5.0.1",
+ "webpack-dev-server": "^4.6.0",
+ "webpack-merge": "^5.8.0"
+ }
+}
diff --git a/examples/helia-service-worker-gateway/public/favicon.ico b/examples/helia-service-worker-gateway/public/favicon.ico
new file mode 100644
index 00000000..b2f1f968
Binary files /dev/null and b/examples/helia-service-worker-gateway/public/favicon.ico differ
diff --git a/examples/helia-service-worker-gateway/public/index.html b/examples/helia-service-worker-gateway/public/index.html
new file mode 100644
index 00000000..97483b22
--- /dev/null
+++ b/examples/helia-service-worker-gateway/public/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+ <%= htmlWebpackPlugin.options.title %>
+
+
+
+
+
+
+
+
diff --git a/examples/helia-service-worker-gateway/src/app.css b/examples/helia-service-worker-gateway/src/app.css
new file mode 100644
index 00000000..1c9bd170
--- /dev/null
+++ b/examples/helia-service-worker-gateway/src/app.css
@@ -0,0 +1,42 @@
+::placeholder {
+ color: rgb(0 0 0 / 30%);
+}
+
+form {
+ margin: 1.25rem 0;
+}
+
+.window {
+ display: flex;
+ flex-direction: column;
+ background: #222;
+ color: #fff;
+ height: 400px;
+}
+
+.window .header {
+ flex-basis: 3rem;
+ background: #c6c6c6;
+ position: relative;
+}
+
+.window .header:after {
+ content: ". . .";
+ position: absolute;
+ left: 12px;
+ right: 0;
+ top: -3px;
+ font-family: "Times New Roman", Times, serif;
+ font-size: 96px;
+ color: #fff;
+ line-height: 0;
+ letter-spacing: -12px;
+}
+
+.terminal {
+ margin: 20px;
+ font-family: monospace;
+ font-size: 16px;
+ overflow: auto;
+ flex: 1;
+}
diff --git a/examples/helia-service-worker-gateway/src/app.js b/examples/helia-service-worker-gateway/src/app.js
new file mode 100644
index 00000000..dbe4ff5b
--- /dev/null
+++ b/examples/helia-service-worker-gateway/src/app.js
@@ -0,0 +1,134 @@
+// @ts-check
+import React, { useState, useRef } from 'react';
+import ipfsLogo from './ipfs-logo.svg'
+import { getHelia } from './get-helia.js'
+import { unixfs } from '@helia/unixfs'
+import { CID } from 'multiformats/cid'
+
+function App() {
+ const [output, setOutput] = useState([]);
+ const [helia, setHelia] = useState(null);
+ const [fileCid, setFileCid] = useState('');
+
+ const terminalEl = useRef(null);
+
+ const COLORS = {
+ active: '#357edd',
+ success: '#0cb892',
+ error: '#ea5037'
+ }
+
+ const showStatus = (text, color, id) => {
+ setOutput((prev) => {
+ return [...prev,
+ {
+ 'content': text,
+ 'color': color,
+ 'id': id
+ }
+ ]
+ })
+
+ terminalEl.current.scroll({ top: terminal.scrollHeight, behavior: 'smooth' })
+ }
+
+ const getFile = async (fileCid) => {
+ let node = helia;
+
+ if (!helia) {
+ showStatus('Creating Helia node...', COLORS.active)
+
+ node = await getHelia()
+
+ setHelia(node)
+ }
+
+ const peerId = node.libp2p.peerId
+ console.log(peerId)
+ showStatus(`My ID is ${peerId}`, COLORS.active, peerId)
+
+ const fs = unixfs(node)
+ const cid = CID.parse(fileCid)
+
+ showStatus(`Reading UnixFS text file ${cid}...`, COLORS.active)
+ const decoder = new TextDecoder()
+ let text = ''
+
+ for await (const chunk of fs.cat(cid)) {
+ text += decoder.decode(chunk, {
+ stream: true
+ })
+ }
+
+ showStatus(`\u2514\u2500 CID: ${cid}`)
+ showStatus(`Preview: https://ipfs.io/ipfs/${cid}`, COLORS.success)
+ }
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+
+ try {
+ if (fileCid == null || fileCid.trim() === '') {
+ throw new Error('File CID is missing...')
+ }
+
+ await getFile(fileCid)
+ } catch (err) {
+ showStatus(err.message, COLORS.error)
+ }
+ }
+
+ return (
+ <>
+
+
+
+ Add data to Helia from the browser
+
+
+
+ Output
+
+
+
+
+ { output.length > 0 &&
+
+ { output.map((log, index) =>
+
+ {log.content}
+
)
+ }
+
+ }
+
+
+
+ >
+ );
+}
+
+export default App;
diff --git a/examples/helia-service-worker-gateway/src/get-helia.js b/examples/helia-service-worker-gateway/src/get-helia.js
new file mode 100644
index 00000000..d58ea87d
--- /dev/null
+++ b/examples/helia-service-worker-gateway/src/get-helia.js
@@ -0,0 +1,80 @@
+import { createHelia } from 'helia'
+import { createLibp2p } from 'libp2p'
+import { noise } from '@chainsafe/libp2p-noise'
+import { yamux } from '@chainsafe/libp2p-yamux'
+import { webSockets } from '@libp2p/websockets'
+import { webTransport } from '@libp2p/webtransport'
+import { bootstrap } from '@libp2p/bootstrap'
+import { MemoryBlockstore } from 'blockstore-core'
+import { MemoryDatastore } from 'datastore-core'
+import { delegatedPeerRouting } from "@libp2p/delegated-peer-routing";
+import { delegatedContentRouting } from "@libp2p/delegated-content-routing";
+import { create as kuboClient } from "kubo-rpc-client";
+
+export async function getHelia () {
+ // the blockstore is where we store the blocks that make up files
+ const blockstore = new MemoryBlockstore()
+
+ // application-specific data lives in the datastore
+ const datastore = new MemoryDatastore()
+
+ // default is to use ipfs.io
+ const delegatedClient = kuboClient({
+ // use default api settings
+ protocol: "https",
+ port: 443,
+ host: "node3.delegate.ipfs.io",
+})
+
+ // libp2p is the networking layer that underpins Helia
+ const libp2p = await createLibp2p({
+ datastore,
+ transports: [
+ webSockets(), webTransport()
+ ],
+ connectionEncryption: [
+ noise()
+ ],
+ streamMuxers: [
+ yamux()
+ ],
+ peerRouters: [delegatedPeerRouting(delegatedClient)],
+ contentRouters: [delegatedContentRouting(delegatedClient)],
+ /**
+ * @see https://github.com/libp2p/js-libp2p/blob/master/doc/CONFIGURATION.md#configuring-dialing
+ */
+ dialer: {
+ dialTimeout: 120000,
+ },
+ /**
+ * @see https://github.com/libp2p/js-libp2p/blob/master/doc/CONFIGURATION.md#configuring-connection-manager
+ */
+ connectionManager: {
+ // Auto connect to discovered peers (limited by ConnectionManager minConnections)
+ autoDial: true,
+ // maxConnections: 10,
+ // minConnections: 0,
+ // pollInterval: 2000,
+ },
+ /**
+ * @see https://github.com/libp2p/js-libp2p/blob/master/doc/CONFIGURATION.md#configuring-peerstore
+ */
+ peerStore: {
+ persistence: true,
+ /**
+ * The threshold number represents the maximum number of "dirty peers" allowed in the PeerStore, i.e. peers that
+ * are not updated in the datastore. In this context, browser nodes should use a threshold of 1, since they
+ * might not "stop" properly in several scenarios and the PeerStore might end up with unflushed records when the
+ * window is closed.
+ */
+ threshold: 1,
+ },
+ })
+
+ // create a Helia node
+ return await createHelia({
+ datastore,
+ blockstore,
+ libp2p
+ })
+}
diff --git a/examples/helia-service-worker-gateway/src/index.js b/examples/helia-service-worker-gateway/src/index.js
new file mode 100644
index 00000000..716ccedf
--- /dev/null
+++ b/examples/helia-service-worker-gateway/src/index.js
@@ -0,0 +1,11 @@
+import React from 'react'
+import ReactDOM from 'react-dom'
+import './app.css';
+import App from './app.js'
+
+ReactDOM.render(
+
+
+ ,
+ document.getElementById('root')
+);
diff --git a/examples/helia-service-worker-gateway/src/ipfs-logo.svg b/examples/helia-service-worker-gateway/src/ipfs-logo.svg
new file mode 100644
index 00000000..f8633f62
--- /dev/null
+++ b/examples/helia-service-worker-gateway/src/ipfs-logo.svg
@@ -0,0 +1 @@
+
diff --git a/examples/helia-service-worker-gateway/tests/test.js b/examples/helia-service-worker-gateway/tests/test.js
new file mode 100644
index 00000000..3c09a556
--- /dev/null
+++ b/examples/helia-service-worker-gateway/tests/test.js
@@ -0,0 +1,37 @@
+import { test, expect } from '@playwright/test';
+import { playwright } from 'test-util-ipfs-example';
+
+// Setup
+const play = test.extend({
+ ...playwright.servers(),
+});
+
+play.describe('bundle Helia with Webpack:', () => {
+ // DOM
+ const nameInput = "#file-name"
+ const contentInput = "#file-content"
+ const submitBtn = "#add-submit"
+ const output = "#output"
+
+ play.beforeEach(async ({servers, page}) => {
+ await page.goto(`http://localhost:${servers[0].port}/`);
+ })
+
+ play('should properly initialized a Helia node and add/get a file', async ({ page }) => {
+ const fileName = 'test.txt'
+ const stringToUse = 'Hello world!'
+
+ await page.fill(nameInput, fileName)
+ await page.fill(contentInput, stringToUse)
+ await page.click(submitBtn)
+
+ await page.waitForSelector(`${output}:has-text("/bafkreigaknpexyvxt76zgkitavbwx6ejgfheup5oybpm77f3pxzrvwpfdi")`)
+
+ const outputContent = await page.textContent(output)
+
+ expect(outputContent).toContain("bafkreigaknpexyvxt76zgkitavbwx6ejgfheup5oybpm77f3pxzrvwpfdi");
+ expect(outputContent).toContain("https://ipfs.io/ipfs/bafkreigaknpexyvxt76zgkitavbwx6ejgfheup5oybpm77f3pxzrvwpfdi");
+ expect(outputContent).toContain(fileName);
+ expect(outputContent).toContain(stringToUse);
+ });
+});
diff --git a/examples/helia-service-worker-gateway/webpack.config.js b/examples/helia-service-worker-gateway/webpack.config.js
new file mode 100644
index 00000000..b175fa27
--- /dev/null
+++ b/examples/helia-service-worker-gateway/webpack.config.js
@@ -0,0 +1,170 @@
+import path from 'path'
+import webpack from 'webpack'
+import { merge } from 'webpack-merge'
+import { fileURLToPath } from 'url'
+import CopyWebpackPlugin from 'copy-webpack-plugin'
+import NodePolyfillPlugin from 'node-polyfill-webpack-plugin'
+import HtmlWebpackPlugin from 'html-webpack-plugin'
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url))
+
+/**
+ * HMR/Live Reloading broken after Webpack 5 rc.0 -> rc.1 update
+ * https://github.com/webpack/webpack-dev-server/issues/2758
+ *
+ * target: 'web' for the moment under your development mode.
+ */
+
+const paths = {
+ // Source files
+ src: path.resolve(__dirname, './src'),
+
+ // Production build files
+ build: path.resolve(__dirname, './dist'),
+
+ // Static files that get copied to build folder
+ public: path.resolve(__dirname, './public')
+}
+
+const prod = {
+ mode: 'production',
+ devtool: false,
+ output: {
+ path: paths.build,
+ publicPath: '/',
+ filename: '[name].[contenthash].bundle.js'
+ },
+ performance: {
+ hints: false,
+ maxEntrypointSize: 512000,
+ maxAssetSize: 512000
+ },
+ // fix: https://github.com/webpack/webpack-dev-server/issues/2758
+ target: 'browserslist'
+}
+
+const dev = {
+ // Set the mode to development or production
+ mode: 'development',
+
+ // Control how source maps are generated
+ devtool: 'inline-source-map',
+
+ // Where webpack outputs the assets and bundles
+ output: {
+ path: paths.build,
+ filename: '[name].bundle.js',
+ publicPath: '/'
+ },
+
+ // Spin up a server for quick development
+ devServer: {
+ historyApiFallback: true,
+ static: paths.build,
+ open: true,
+ compress: true,
+ hot: true,
+ port: 3000
+ },
+
+ plugins: [
+ // Only update what has changed on hot reload
+ new webpack.HotModuleReplacementPlugin()
+ ]
+}
+
+const common = {
+// Where webpack looks to start building the bundle
+ entry: [paths.src + '/index.js'],
+
+ // Customize the webpack build process
+ plugins: [
+ // Copies files from target to destination folder
+ new CopyWebpackPlugin({
+ patterns: [
+ {
+ from: `${paths.public}/assets`,
+ to: 'assets',
+ globOptions: {
+ ignore: ['*.DS_Store']
+ },
+ noErrorOnMissing: true
+ }
+ ]
+ }),
+
+ // fixes Module not found: Error: Can't resolve 'stream' in '.../node_modules/nofilter/lib'
+ new NodePolyfillPlugin(),
+ // Note: stream-browserify has assumption about `Buffer` global in its
+ // dependencies causing runtime errors. This is a workaround to provide
+ // global `Buffer` until https://github.com/isaacs/core-util-is/issues/29
+ // is fixed.
+ new webpack.ProvidePlugin({
+ Buffer: ['buffer', 'Buffer'],
+ process: 'process/browser'
+ }),
+
+ // Generates an HTML file from a template
+ // Generates deprecation warning: https://github.com/jantimon/html-webpack-plugin/issues/1501
+ new HtmlWebpackPlugin({
+ title: 'Helia bundle by Webpack',
+ favicon: paths.public + '/favicon.ico',
+ template: paths.public + '/index.html', // template file
+ filename: 'index.html', // output file,
+ minify: false
+ })
+ ],
+
+ // Determine how modules within the project are treated
+ module: {
+ rules: [
+ // JavaScript: Use Babel to transpile JavaScript files
+ {
+ test: /\.js$/,
+ exclude: /node_modules/,
+ use: {
+ loader: 'babel-loader',
+ options: {
+ presets: [
+ [
+ '@babel/preset-env',
+ {
+ targets: {
+ esmodules: true
+ }
+ }
+ ],
+ '@babel/preset-react'
+ ]
+ }
+ }
+ },
+
+ // Images: Copy image files to build folder
+ { test: /\.(?:ico|gif|png|jpg|jpeg)$/i, type: 'asset/resource' },
+
+ // Fonts and SVGs: Inline files
+ { test: /\.(woff(2)?|eot|ttf|otf|svg|)$/, type: 'asset/inline' },
+
+ { test: /\.(css)$/, use: ['style-loader','css-loader'] }
+ ]
+ },
+
+ resolve: {
+ modules: [paths.src, 'node_modules'],
+ extensions: ['.js', '.jsx', '.json'],
+ alias: {
+ '@': paths.src
+ }
+ },
+
+ // fix: https://github.com/webpack/webpack-dev-server/issues/2758
+ target: 'web'
+}
+
+export default (cmd) => {
+ const production = cmd.production
+ const config = production ? prod : dev
+
+ return merge(common, config)
+}