├── .babelrc.js ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github ├── config.yaml └── workflows │ ├── check.yaml │ ├── publish-lib.yaml │ ├── release_github.yaml │ ├── release_lib_github.yaml │ └── tests.yaml ├── .gitignore ├── .huskyrc ├── .prettierrc ├── .vscode └── settings.json ├── CHANGELOG.md ├── CODEOWNERS ├── PRIVACY_POLICY.md ├── README.md ├── assets └── swarm.png ├── commitlint.config.js ├── jest.config.ts ├── library ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── CHANGELOG.md ├── README.md ├── package-lock.json ├── package.json ├── src │ ├── constants │ │ └── events.ts │ ├── index.ts │ ├── messages │ │ ├── dapp-swarm-messages.ts │ │ ├── e2e-swarm-messages.ts │ │ ├── swarm-messages.factory.ts │ │ └── swarm-messages.ts │ ├── model │ │ ├── flavored.type.ts │ │ ├── general.types.ts │ │ ├── local-storage.interface.ts │ │ ├── messages.model.ts │ │ ├── postage-batch.interface.ts │ │ └── web2-helper.interface.ts │ ├── services │ │ ├── bzz-link.ts │ │ ├── local-storage.ts │ │ ├── postage-batch.ts │ │ ├── session.ts │ │ └── web2-helper.content.ts │ ├── swarm-html.ts │ ├── swarm.ts │ └── utils │ │ ├── bzz-link.ts │ │ ├── environment.util.ts │ │ ├── fake-url.ts │ │ └── window.util.ts ├── tsconfig.json └── webpack.config.ts ├── manifest.json ├── package-lock.json ├── package.json ├── scripts └── tests-copy-library.sh ├── src ├── background │ ├── constants │ │ ├── addresses.ts │ │ └── message-keys.enum.ts │ ├── dapp-session.manager.ts │ ├── feeder │ │ ├── dapp-session.feeder.ts │ │ ├── debug.feeder.ts │ │ ├── e2e-session.feeder.ts │ │ ├── local-storage.feeder.ts │ │ ├── postage-batch.feeder.ts │ │ └── web2-helper.feeder.ts │ ├── index.ts │ ├── listener │ │ ├── bee-api.listener.ts │ │ └── debug.listener.ts │ ├── live-reload │ │ └── live-reload.ts │ ├── model │ │ ├── dapp-security-context.model.ts │ │ └── model-typeguards.ts │ └── utils │ │ └── index.ts ├── contentscript │ └── document-start │ │ ├── index.ts │ │ └── library-listener.ts ├── index.ts ├── popup-page │ ├── assets │ │ ├── iAWriterMonoV.ttf │ │ └── iAWriterQuattroV.ttf │ ├── context │ │ └── global.tsx │ ├── index.css │ ├── index.html │ ├── index.tsx │ ├── modules │ │ ├── BeeEndpoints.tsx │ │ ├── Button.tsx │ │ ├── GlobalPostageStamp.tsx │ │ ├── Input.tsx │ │ ├── Logo.tsx │ │ ├── Row.tsx │ │ ├── Toggle.tsx │ │ ├── Web2Origin.tsx │ │ └── app.tsx │ └── utils │ │ └── index.ts └── utils │ ├── bee-js.ts │ ├── bzz-link.ts │ ├── error-with-console-log.ts │ ├── fake-url.ts │ ├── message │ ├── dapp-session │ │ └── dapp-session.message.ts │ ├── local-storage │ │ └── index.ts │ ├── message-handler.ts │ ├── postage-batch │ │ └── postage-batch.message.ts │ └── web2-helper │ │ └── web2-helper.message.ts │ └── storage.ts ├── test ├── bzz-protocol.spec.ts ├── bzz-test-page │ ├── bee-js.min.js │ ├── index.css │ ├── index.html │ ├── index.js │ ├── jafar-page │ │ ├── images │ │ │ └── jinn.png │ │ └── index.html │ ├── jinn-page │ │ ├── images │ │ │ ├── jafar-jinn.png │ │ │ └── jinn.png │ │ └── index.html │ └── local-storage │ │ └── index.html ├── config │ ├── puppeteer_environment.js │ ├── setup.ts │ └── teardown.ts └── utils.ts ├── tsconfig.json ├── types ├── index.d.ts └── webextension-polyfill │ └── index.d.ts └── webpack.config.ts /.babelrc.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (api) { 4 | const targets = '>1% and not ie 11 and not dead' 5 | api.cache(true) 6 | api.cacheDirectory = true 7 | 8 | return { 9 | presets: [ 10 | '@babel/preset-typescript', 11 | [ 12 | '@babel/preset-env', 13 | { 14 | corejs: 3, 15 | useBuiltIns: 'entry', 16 | modules: 'commonjs', 17 | bugfixes: true, 18 | targets 19 | } 20 | ], 21 | '@babel/preset-react' 22 | ], 23 | plugins: [ 24 | '@babel/plugin-proposal-class-properties', 25 | [ 26 | '@babel/plugin-transform-runtime', 27 | { 28 | helpers: false, 29 | regenerator: true 30 | } 31 | ] 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | end_of_line = lf 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/** 2 | test/**/swarm/** 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['plugin:@typescript-eslint/recommended', 'prettier', 'plugin:prettier/recommended'], 3 | parserOptions: { 4 | sourceType: 'module', 5 | ecmaVersion: 2018, 6 | }, 7 | env: { 8 | jest: true, 9 | }, 10 | globals: { 11 | browser: true, 12 | page: true, 13 | }, 14 | plugins: ['jest'], 15 | rules: { 16 | 'array-bracket-newline': ['error', { multiline: true }], 17 | strict: ['error', 'safe'], 18 | curly: 'error', 19 | 'block-scoped-var': 'error', 20 | complexity: 'warn', 21 | 'default-case': 'error', 22 | 'dot-notation': 'warn', 23 | eqeqeq: 'error', 24 | 'guard-for-in': 'warn', 25 | 'linebreak-style': ['warn', 'unix'], 26 | 'no-alert': 'error', 27 | 'no-case-declarations': 'error', 28 | 'no-constant-condition': 'error', 29 | 'no-div-regex': 'error', 30 | 'no-empty': 'warn', 31 | 'no-empty-pattern': 'error', 32 | 'no-implicit-coercion': 'error', 33 | 'prefer-arrow-callback': 'warn', 34 | 'no-labels': 'error', 35 | 'no-loop-func': 'error', 36 | 'no-nested-ternary': 'warn', 37 | 'no-script-url': 'error', 38 | 'no-warning-comments': 'warn', 39 | 'quote-props': ['error', 'as-needed'], 40 | 'require-yield': 'error', 41 | 'max-nested-callbacks': ['error', 4], 42 | 'max-depth': ['error', 4], 43 | 'require-await': 'error', 44 | '@typescript-eslint/no-non-null-assertion': 'off', 45 | 'space-before-function-paren': [ 46 | 'error', 47 | { 48 | anonymous: 'never', 49 | named: 'never', 50 | asyncArrow: 'always', 51 | }, 52 | ], 53 | 'padding-line-between-statements': [ 54 | 'error', 55 | { blankLine: 'always', prev: '*', next: 'if' }, 56 | { blankLine: 'always', prev: '*', next: 'function' }, 57 | { blankLine: 'always', prev: '*', next: 'return' }, 58 | ], 59 | 'no-useless-constructor': 'off', 60 | 'no-dupe-class-members': 'off', 61 | 'no-unused-expressions': 'off', 62 | curly: ['error', 'multi-line'], 63 | 'object-curly-spacing': ['error', 'always'], 64 | 'comma-dangle': ['error', 'always-multiline'], 65 | '@typescript-eslint/no-useless-constructor': 'error', 66 | '@typescript-eslint/no-unused-expressions': 'error', 67 | '@typescript-eslint/member-delimiter-style': [ 68 | 'error', 69 | { 70 | multiline: { 71 | delimiter: 'none', 72 | requireLast: true, 73 | }, 74 | singleline: { 75 | delimiter: 'semi', 76 | requireLast: false, 77 | }, 78 | }, 79 | ], 80 | }, 81 | overrides: [ 82 | { 83 | files: ['*.spec.ts'], 84 | rules: { 85 | // '@typescript-eslint/ban-ts-ignore': 'off', 86 | 'max-nested-callbacks': ['error', 10], // allow describe/it nesting 87 | }, 88 | }, 89 | ], 90 | } 91 | -------------------------------------------------------------------------------- /.github/config.yaml: -------------------------------------------------------------------------------- 1 | labels: 2 | issue: type:issue 3 | pull-request: type:pull-request 4 | release: 5 | trigger: 6 | labels: 7 | - 'autorelease: pending' 8 | checklist: | 9 | ## 🚀 Release checklist 10 | 11 | ### Before release 12 | 13 | - [ ] Update the version in `manifest.json` to the same version as this PR 14 | - [ ] Update Google Store image before publish if the popup page's UI structure changed 15 | -------------------------------------------------------------------------------- /.github/workflows/check.yaml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | pull_request: 8 | branches: 9 | - '**' 10 | 11 | jobs: 12 | check: 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | matrix: 17 | node-version: [16.x] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | with: 22 | fetch-depth: 0 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v2 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | 28 | ## Try getting the node modules from cache, if failed npm ci 29 | - uses: actions/cache@v2 30 | id: cache-npm 31 | with: 32 | path: node_modules 33 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 34 | restore-keys: | 35 | ${{ runner.OS }}-node-${{ env.cache-name }}- 36 | ${{ runner.OS }}-node- 37 | ${{ runner.OS }}- 38 | - name: Install npm deps 39 | if: steps.cache-npm.outputs.cache-hit != 'true' 40 | run: npm ci 41 | 42 | - name: Commit linting 43 | uses: wagoid/commitlint-github-action@v2 44 | 45 | - name: Code linting 46 | run: npm run lint:check 47 | env: 48 | CI: true 49 | 50 | - name: Build Browser Extension 51 | run: npm run compile 52 | -------------------------------------------------------------------------------- /.github/workflows/publish-lib.yaml: -------------------------------------------------------------------------------- 1 | name: Build and publish JS library 2 | 3 | on: 4 | release: 5 | types: [published] 6 | tags-ignore: 7 | - swarm-extension* 8 | workflow_dispatch: 9 | 10 | jobs: 11 | publish-lib: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 1 19 | 20 | - name: Use Node.js 16 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: 16 24 | registry-url: 'https://registry.npmjs.org' 25 | 26 | - name: Cache library node modules 27 | id: cache-npm-lib 28 | uses: actions/cache@v3 29 | with: 30 | path: library/node_modules 31 | key: ${{ runner.os }}-lib-${{ matrix.node }}-${{ hashFiles('**/library/package-lock.json') }} 32 | restore-keys: | 33 | ${{ runner.OS }}-lib-${{ matrix.node }}-${{ env.cache-name }}- 34 | ${{ runner.OS }}-lib-${{ matrix.node }}- 35 | 36 | - name: Install library dependencies 37 | run: npm ci --prefix library 38 | if: ${{ steps.cache-npm-lib.outputs.cache-hit != 'true' }} 39 | 40 | - name: Build 41 | run: npm run build --prefix library 42 | 43 | - name: Publish 44 | run: cd library && npm publish --access public && cd .. 45 | env: 46 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 47 | -------------------------------------------------------------------------------- /.github/workflows/release_github.yaml: -------------------------------------------------------------------------------- 1 | # On each new commit to master, create/update a PR with release 2 | # automatically bumps version and creates changelog as per conventional commits 3 | name: Release Github 4 | 5 | on: 6 | push: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | release-please: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: GoogleCloudPlatform/release-please-action@v3 15 | id: release 16 | with: 17 | token: ${{ secrets.GHA_PAT_BASIC }} 18 | release-type: node 19 | package-name: swarm-extension 20 | monorepo-tags: true 21 | bump-minor-pre-major: true 22 | pull-request-title-pattern: 'chore${scope}: release swarm-extension ${version}' 23 | -------------------------------------------------------------------------------- /.github/workflows/release_lib_github.yaml: -------------------------------------------------------------------------------- 1 | # On each new commit to master, create/update a PR with release 2 | # automatically bumps version and creates changelog as per conventional commits 3 | # https://github.com/google-github-actions/release-please-action 4 | name: Release Github 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | release-please-lib: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: google-github-actions/release-please-action@v3 16 | id: release-lib 17 | with: 18 | release-type: node 19 | path: library 20 | package-name: 'swarm-library' 21 | changelog-path: 'CHANGELOG.md' 22 | version-file: 'package.json' 23 | pull-request-title-pattern: 'chore${scope}: release swarm-extension-library ${version}' 24 | monorepo-tags: true 25 | bump-minor-pre-major: true 26 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | pull_request: 8 | branches: 9 | - '**' 10 | 11 | env: 12 | BEE_API_URL: 'http://172.18.0.1:1633' 13 | BEE_DEBUG_API_URL: 'http://172.18.0.1:1635' 14 | BEE_PEER_API_URL: 'http://172.18.0.1:11633' 15 | BEE_PEER_DEBUG_API_URL: 'http://172.18.0.1:11635' 16 | BEE_VERSION: '1.8.2' 17 | 18 | jobs: 19 | browser: 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | matrix: 24 | node-version: [16.x] 25 | 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@v2 29 | with: 30 | fetch-depth: 1 31 | 32 | - name: Use Node.js ${{ matrix.node-version }} 33 | uses: actions/setup-node@v3 34 | with: 35 | node-version: ${{ matrix.node-version }} 36 | cache: 'npm' 37 | 38 | - name: Auth to Github Package Docker Registry 39 | run: | 40 | echo "${{ secrets.GITHUB_TOKEN }}" | docker login https://docker.pkg.github.com -u ${GITHUB_ACTOR} --password-stdin 41 | - name: Install npm deps 42 | if: steps.cache-npm.outputs.cache-hit != 'true' 43 | run: npm ci 44 | 45 | - name: Compile 46 | run: npm run compile 47 | 48 | - name: Install library npm deps 49 | if: steps.cache-npm-lib.outputs.cache-hit != 'true' 50 | run: cd library && npm ci && cd .. 51 | 52 | - name: Build library 53 | run: cd library && npm run build && cd .. 54 | 55 | - name: Install fdp-play 56 | run: npm install -g @fairdatasociety/fdp-play 57 | 58 | - name: Run fdp-play 59 | run: fdp-play start -d --bee-version $BEE_VERSION 60 | 61 | - name: Run tests 62 | uses: vojtechsimetka/puppeteer-headful@master 63 | env: 64 | CI: 'true' 65 | with: 66 | args: npm run test 67 | 68 | - name: Debug workflow if failed 69 | if: failure() 70 | run: | 71 | KEYS=$(curl -sSf -X POST https://relay.tunshell.com/api/sessions) 72 | curl -sSf -X POST -H "Content-Type: application/json" -d "{\"text\": \"**Bee JS**\nDebug -> \`sh <(curl -sSf https://lets.tunshell.com/init.sh) L $(echo $KEYS | jq -r .peer2_key) \${TUNSHELL_SECRET} relay.tunshell.com\`\"}" https://beehive.ethswarm.org/hooks/${{ secrets.WEBHOOK_KEY }} 73 | echo "Connect to github actions node using" 74 | echo "sh <(curl -sSf https://lets.tunshell.com/init.sh) L $(echo $KEYS | jq -r .peer2_key) \${TUNSHELL_SECRET} relay.tunshell.com" 75 | curl -sSf https://lets.tunshell.com/init.sh | sh /dev/stdin T $(echo $KEYS | jq -r .peer1_key) ${{ secrets.TUNSHELL_SECRET }} relay.tunshell.com 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | 3 | # Logs 4 | logs 5 | *.log 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | .nyc_output 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # builds 23 | build 24 | dist 25 | extension.zip 26 | 27 | # Dependency directory 28 | node_modules 29 | 30 | # test library 31 | test/**/swarm 32 | -------------------------------------------------------------------------------- /.huskyrc: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "bracketSpacing": true, 6 | "semi": false, 7 | "singleQuote": true, 8 | "quoteProps": "as-needed", 9 | "trailingComma": "all", 10 | "endOfLine": "lf", 11 | "arrowParens": "avoid", 12 | "proseWrap": "always" 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | // For ESLint 4 | "source.fixAll.eslint": true, 5 | // For TSLint 6 | "source.fixAll.tslint": true, 7 | // For Stylelint 8 | "source.fixAll.stylelint": true 9 | }, 10 | "eslint.validate": [ 11 | "javascript", 12 | "typescript" 13 | ], 14 | "editor.formatOnSave": true, 15 | "[javascript]": { 16 | "editor.formatOnSave": false 17 | }, 18 | "[typescript]": { 19 | "editor.defaultFormatter": "dbaeumer.vscode-eslint", 20 | "editor.codeActionsOnSave": { 21 | "source.fixAll.eslint": true 22 | }, 23 | "editor.formatOnSave": true 24 | }, 25 | "[json]": { 26 | "editor.defaultFormatter": "esbenp.prettier-vscode", 27 | "editor.codeActionsOnSave": { 28 | "source.fixAll.eslint": true 29 | }, 30 | "editor.formatOnSave": true 31 | }, 32 | "[markdown]": { 33 | "editor.formatOnSave": false 34 | }, 35 | "search.exclude": { 36 | "**/node_modules": true, 37 | "**/dist": true, 38 | "**/coverage": true 39 | }, 40 | "typescript.referencesCodeLens.enabled": true, 41 | "jest.runAllTestsFirst": false 42 | } 43 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.7.1](https://github.com/ethersphere/swarm-extension/compare/swarm-extension-v0.7.0...swarm-extension-v0.7.1) (2022-11-24) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * bzz.link redirection subdomain logic ([66ad510](https://github.com/ethersphere/swarm-extension/commit/66ad510aa837510e79b1b9860034f39abe68888b)) 9 | * subdomain redirection ([#158](https://github.com/ethersphere/swarm-extension/issues/158)) ([#160](https://github.com/ethersphere/swarm-extension/issues/160)) ([76718e2](https://github.com/ethersphere/swarm-extension/commit/76718e2be5226002ff62ced44d4bae2dba28232e)) 10 | 11 | ## 0.7.0 (2022-11-18) 12 | 13 | Since manifest v2 extensions won't be allowed from June 2023, the Swarm Extension now supports manifest v3. But that brings some limitations, not present in v2. Here are the key changes in v3: 14 | 15 | - The `swarm` object won't be injected into dApp pages. Instead each dApp should include the [Swarm Extension Library](library/README.md) into its code to comunicate with the extension. 16 | - Blocking interceptors are not allowed in manifest v3, so the new implementation uses the [Declarative Network Request API](https://developer.chrome.com/docs/extensions/reference/declarativeNetRequest/). This requirement prevents the extension from checking session ID for fake URL requests. That means the extension cannot check the security context of the links that are being accessed. 17 | - If bee URL is set to `localhost`, then links are redirected to subdomain based bee URLs. For example, trying to access the `bzz://site.eth` URL will result in accessing the `http://site.swarm.localhost:1633/` URL. 18 | 19 | ### Features 20 | 21 | * manifest v3 ([#142](https://github.com/ethersphere/swarm-extension/issues/142)) ([287edee](https://github.com/ethersphere/swarm-extension/commit/287edee31a0cc85e1803aba121d22383389333e6)) 22 | * remove session-id from url ([#150](https://github.com/ethersphere/swarm-extension/pull/150)) 23 | * add subdomain redirection ([#147](https://github.com/ethersphere/swarm-extension/pull/147)) 24 | 25 | ## [0.6.0](https://github.com/ethersphere/swarm-extension/compare/v0.5.0...v0.6.0) (2022-10-04) 26 | 27 | The bzz.link and bzz:// URLs will be redirected to the http://{cid}.localhost:{port} address in case of locally running Bee node. 28 | It allows to leverage the basic security context handling on subdomains for dApps that does not require sandbox rendering anymore. 29 | 30 | The Bee Dashboard recently released a new version that again supports running in browser environment. 31 | Because of that, this dependency has been updated in Swarm Extension that fixes some of the problems with older version. 32 | 33 | ### Features 34 | 35 | * localhost subdomain redirection ([#136](https://github.com/ethersphere/swarm-extension/issues/136)) ([082f053](https://github.com/ethersphere/swarm-extension/commit/082f053dd9b59edff33e922898234a2820fdcc2e)) 36 | 37 | 38 | ### Bug Fixes 39 | 40 | * bee dashboard ([#143](https://github.com/ethersphere/swarm-extension/issues/143)) ([225aec5](https://github.com/ethersphere/swarm-extension/commit/225aec525c40dcf0199c30d8c9dce44feea17e41)) 41 | 42 | ## [0.5.0](https://github.com/ethersphere/swarm-extension/compare/v0.4.0...v0.5.0) (2022-07-06) 43 | 44 | 45 | ### Features 46 | 47 | * add method to check if global postage batch is enabled ([#122](https://github.com/ethersphere/swarm-extension/issues/122)) ([#128](https://github.com/ethersphere/swarm-extension/issues/128)) ([b56536f](https://github.com/ethersphere/swarm-extension/commit/b56536f51266d223a1f22ad35d556f95d02e87b7)) 48 | * e2e api ([#102](https://github.com/ethersphere/swarm-extension/issues/102)) ([f0091b2](https://github.com/ethersphere/swarm-extension/commit/f0091b2d4b545b00b8b5b2e2511449b6bbe76d7f)) 49 | 50 | ## [0.4.0](https://github.com/ethersphere/swarm-extension/compare/v0.3.1...v0.4.0) (2022-06-30) 51 | 52 | 53 | ### Features 54 | 55 | * add option to check whether bee api is available ([#127](https://github.com/ethersphere/swarm-extension/issues/127)) ([34719b0](https://github.com/ethersphere/swarm-extension/commit/34719b03d9ca1cfb921a0fd14bcad605a810e1d1)) 56 | * new swarm html elements ([#124](https://github.com/ethersphere/swarm-extension/issues/124)) ([2ce4f00](https://github.com/ethersphere/swarm-extension/commit/2ce4f00b1fa27787c7f54d668703729656a713d1)) 57 | 58 | 59 | ### Bug Fixes 60 | 61 | * global postage batch ([#125](https://github.com/ethersphere/swarm-extension/issues/125)) ([21be2dc](https://github.com/ethersphere/swarm-extension/commit/21be2dce8627d9e251dc231736ea0315c129c80e)) 62 | * set global postage batch on fetch ([#121](https://github.com/ethersphere/swarm-extension/issues/121)) ([f392b7c](https://github.com/ethersphere/swarm-extension/commit/f392b7c50de8641a63114f7a9c552a2e0aaaf28a)) 63 | 64 | ## [0.3.1](https://github.com/ethersphere/swarm-extension/compare/v0.3.0...v0.3.1) (2022-06-11) 65 | 66 | 67 | ### Bug Fixes 68 | 69 | * update supported bee version to 1.6.1 with bee-dashboard v0.16.0 ([#119](https://github.com/ethersphere/swarm-extension/issues/119)) ([f808a81](https://github.com/ethersphere/swarm-extension/commit/f808a815b99db44eade0b971ddf62d72703c870b)) 70 | * update swarm dependencies ci 3 ([#116](https://github.com/ethersphere/swarm-extension/issues/116)) ([76141e0](https://github.com/ethersphere/swarm-extension/commit/76141e0d0c39dbe086ca29042a471c7226b0220f)) 71 | 72 | ## 0.3.0 (2022-04-20) 73 | 74 | 75 | ### Features 76 | 77 | * add extension hot reload ([097565c](https://www.github.com/ethersphere/swarm-extension/commit/097565c04387ee12541b73d5f738b4be5d6245f9)) 78 | * bee dashboard integration ([#74](https://www.github.com/ethersphere/swarm-extension/issues/74)) ([eb7ca96](https://www.github.com/ethersphere/swarm-extension/commit/eb7ca96a407d256103b4e9a06c20871b945e8193)) 79 | * bee dashboard v0.12 ([#88](https://www.github.com/ethersphere/swarm-extension/issues/88)) ([51dcac8](https://www.github.com/ethersphere/swarm-extension/commit/51dcac890a1b6bec064dd897492dd5d7c0436b83)) 80 | * bzz protocol ([#1](https://www.github.com/ethersphere/swarm-extension/issues/1)) ([8cc52ca](https://www.github.com/ethersphere/swarm-extension/commit/8cc52ca764f8bfd5d5541e15ea0dc8371c1d94ae)) 81 | * bzz.link ([#51](https://www.github.com/ethersphere/swarm-extension/issues/51)) ([a8a1618](https://www.github.com/ethersphere/swarm-extension/commit/a8a161872ae02f71b3cc4d5fcaaf9a6e3ff8787d)) 82 | * changable bee address ([#9](https://www.github.com/ethersphere/swarm-extension/issues/9)) ([8a5dc40](https://www.github.com/ethersphere/swarm-extension/commit/8a5dc407c48ab149cecb81063d57a7ad90dcf9ba)) 83 | * cross-domain local storage ([#43](https://www.github.com/ethersphere/swarm-extension/issues/43)) ([b2eacc0](https://www.github.com/ethersphere/swarm-extension/commit/b2eacc055c5cd9157c2f02e888fec934c6b6d7a0)) 84 | * csp header ([#47](https://www.github.com/ethersphere/swarm-extension/issues/47)) ([570e283](https://www.github.com/ethersphere/swarm-extension/commit/570e2833611a95cc8b098ef26fd6d292d7c8350e)) 85 | * dapp session id registration ([#26](https://www.github.com/ethersphere/swarm-extension/issues/26)) ([9adbf14](https://www.github.com/ethersphere/swarm-extension/commit/9adbf14f8061406d29483b74cc5e44d24a7f1b5f)) 86 | * fake url ([#22](https://www.github.com/ethersphere/swarm-extension/issues/22)) ([6bedf5b](https://www.github.com/ethersphere/swarm-extension/commit/6bedf5b14752449d82345e45c4a1d6f94f5545cb)) 87 | * global postage stamp ([#40](https://www.github.com/ethersphere/swarm-extension/issues/40)) ([40bddab](https://www.github.com/ethersphere/swarm-extension/commit/40bddabbff5a6bd9ce91ed155dcfc93ad8b4cda8)) 88 | * let's roll! ([b8e552d](https://www.github.com/ethersphere/swarm-extension/commit/b8e552dc66e564661597cf9aec7b0f64712f89f3)) 89 | * messaging ([#16](https://www.github.com/ethersphere/swarm-extension/issues/16)) ([fb49955](https://www.github.com/ethersphere/swarm-extension/commit/fb49955a80e17a1b26d8957eadcdc759263abadd)) 90 | * new bzz link cid format ([#81](https://www.github.com/ethersphere/swarm-extension/issues/81)) ([570edb5](https://www.github.com/ethersphere/swarm-extension/commit/570edb5c8fbc5b9604a26dc6c5d348b9d716988d)) 91 | * postage batch on debug ([#69](https://www.github.com/ethersphere/swarm-extension/issues/69)) ([273a4fb](https://www.github.com/ethersphere/swarm-extension/commit/273a4fba095f5399c2ea3ce2bc8b4fe37f5601eb)) 92 | * session id handling ([#37](https://www.github.com/ethersphere/swarm-extension/issues/37)) ([4af7a83](https://www.github.com/ethersphere/swarm-extension/commit/4af7a83cd4ea4c17bcdc946873a22df8194b8bc4)) 93 | * swarm html ([#29](https://www.github.com/ethersphere/swarm-extension/issues/29)) ([5002d14](https://www.github.com/ethersphere/swarm-extension/commit/5002d14a3898b09aecad3a84b9367c031f539327)) 94 | * upgraded extension design ([#84](https://www.github.com/ethersphere/swarm-extension/issues/84)) ([b2363ae](https://www.github.com/ethersphere/swarm-extension/commit/b2363ae18256a24091de1e0a3c57ae0ceeeb776b)) 95 | 96 | 97 | ### Bug Fixes 98 | 99 | * add ExtraActionsPlugin everywhere ([dc5dc3c](https://www.github.com/ethersphere/swarm-extension/commit/dc5dc3c792b0cf964c0cbdac29b4439b4a81ee6b)) 100 | * fix cross platform replace ([14cc869](https://www.github.com/ethersphere/swarm-extension/commit/14cc869676cc6804395161eb4f3600e00f3625eb)) 101 | * fix too early initialization of bee dashboard component ([#95](https://www.github.com/ethersphere/swarm-extension/issues/95)) ([#99](https://www.github.com/ethersphere/swarm-extension/issues/99)) ([9ac1146](https://www.github.com/ethersphere/swarm-extension/commit/9ac1146f1758538dcc4d7c35ea46b8edb5580f08)) 102 | * hot-reload and compiling ([#31](https://www.github.com/ethersphere/swarm-extension/issues/31)) ([028f1e7](https://www.github.com/ethersphere/swarm-extension/commit/028f1e77f5c4263f501230792fb01f4d07f08924)) 103 | * reinit global postage batch at bee api change ([#58](https://www.github.com/ethersphere/swarm-extension/issues/58)) ([7e1ef6c](https://www.github.com/ethersphere/swarm-extension/commit/7e1ef6c25173bdaf97f05187891086e38d9e80e8)) 104 | * remove compile types and fix declaration file ref ([#19](https://www.github.com/ethersphere/swarm-extension/issues/19)) ([c0563fd](https://www.github.com/ethersphere/swarm-extension/commit/c0563fd9c5fad5f30fabeef9f579e5d57a7607aa)) 105 | * swarm.localStorage mimics window.localStorage with null and undefined set ([#72](https://www.github.com/ethersphere/swarm-extension/issues/72)) ([700ac0c](https://www.github.com/ethersphere/swarm-extension/commit/700ac0ce6dffba03a332f8df0bc727d55df9a923)) 106 | 107 | 108 | ### Continuous Integration 109 | 110 | * bump release version ([#89](https://www.github.com/ethersphere/swarm-extension/issues/89)) ([47a135e](https://www.github.com/ethersphere/swarm-extension/commit/47a135e044bc6e4eb978dde5c28379f9622362c1)) 111 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @nugaon @Cafe137 2 | -------------------------------------------------------------------------------- /PRIVACY_POLICY.md: -------------------------------------------------------------------------------- 1 | # Swarm Extension Privacy Policy 2 | 3 | The Swarm Extension is owned by the Swarm Foundation ("We"). We do not track or collect any of your Personal Data, nor do we sell it to anyone else or use it for advertising. 4 | 5 | By installing Swarm Extension, you accept this privacy policy. 6 | 7 | ## What Does This Privacy Policy Cover? 8 | 9 | This Privacy Policy explains that we don’t gather, track, nor permanently store any of your Personal Data. It also covers Additional Privacy Protocols related to the way the Swarm Extension allows people to add and retrieve data to and from the Swarm Network (which is a peer-to-peer network). This Privacy Policy doesn’t apply to projects run by other organizations outside Swarm Foundation, even if we promote the use of those projects or happen to contribute to them. 10 | 11 | ## What Information Is Collected? 12 | 13 | We do not collect any data about you, including personal data. 14 | 15 | ## Explicitly Shared Data Will Be Publicly Available Over The Swarm Network 16 | 17 | We do not collect, rent, store or sell your Personal Data to anyone. However because Swarm Extension is a web extension that provides access to the real-time, peer-to-peer Swarm Network (which is a public platform for which anyone may join and participate) the data that you import to the Swarm Network using Swarm Extension is then publicly available and accessible to everyone participating in Swarm Network. 18 | 19 | ## Additional Privacy Protocols 20 | 21 | If you add files to the Swarm Network using the Swarm Extension, they will be split into pieces and stored across nodes on the network. Those files are also then cached by anyone who retrieves those files from the Swarm network and co-hosted on that user’s local Swarm Network node. Generally, cached files will eventually expire, but it’s possible for a user with whom you have shared access to such files (by sharing the relevant Content Identifier) to pin that data, which means the cached files then will not expire and will remain stored on such user’s local Swarm Network node. 22 | 23 | All content shared with the Swarm Network is public by default. This means your files and data that you’ve added will be accessible to everyone who knows the Content Identifier or queries the data on the Swarm Network. If you want to share certain materials or data privately, you must encrypt such data before adding it to the Swarm Network. 24 | 25 | Websites will be able to detect you are running Swarm Extension. To disable this behavior use incognito mode or turn off the extension. 26 | 27 | ## Will The Privacy Policy Be Changed? 28 | 29 | We’re constantly trying to improve Swarm Extension, so we may need to change this Privacy Policy sometimes. When we do, we will update it on our [Github page](https://github.com/ethersphere/swarm-extension/commits/main/PRIVACY-POLICY.md) where you will be able to track changes. 30 | We encourage you to periodically review this Privacy Policy to stay informed, which is ultimately your responsibility. If you use Swarm Extension after any changes to the Privacy Policy have been posted, that means you agree to all of those changes. 31 | 32 | ## Where Can I Raise My Questions? 33 | 34 | If you have any questions or concerns regarding the Privacy Policy, please send a message at . 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swarm Extension 2 | 3 | **Warning: This project has Proof of Concept state now. There will be breaking changes continuously in the future. Also, no guarantees can be made about its stability, efficiency, and security at this stage. It only provides a platform currently to show workarounds and examples for the current problems in dApp environments top on Swarm** 4 | 5 | 6 | **Info: For manifest v3 version of the extension, dApps should interact with the extension using the `Swarm Extension Library`. Check how it works [here](library/README.md).** 7 | 8 | This browser extension provides an alternative to have a web3 environment in a web2 browser for users and dApps. 9 | 10 | Users can interact with the Swarm network in two ways: by running their own Bee client or using a gateway solution. 11 | Either of the cases, users can set the connection data of the desired Bee client within the extension in order to channel all Bee client request to that trusted endpoint. 12 | Other settings can be placed regarding how the user wants to interact with the web3 applications, like using one selected postage batch for any file upload attempt of dApps. 13 | 14 | In a web3 architecture the browser is the new server-side backend, therefore frontend applications (dApps) should communicate with it. 15 | For that, there is a pre-defined [API](#fake-url) that dApps can request to and interact with the Bee client of the user in a secure and abstracted manner. 16 | By that, there is no need to assume the user runs a Bee node on the default ports on their computer, or to fallback to a well-known public-gateway when referencing external Swarm resources; just refer to the representative, fixed and abstracted Bee client endpoints that the extension defines [in HTML](#Swarm-HTML) or [in JavaScript](#Custom-Protocol). 17 | The web2 is based on domain-centric considerations, because of that, lot of features cannot be used in a secure way when many applications are loaded from the same (Bee) host. 18 | That is way the extension has its [own Security Context](#dApp-origin-instead-of-host-based-origin) and separates dApps by their root content addresses. 19 | With new Security Context, new governor mechanisms can emerge like [cross-domain localstorage handling](#Cross-Domain-Local-Storage). 20 | 21 | ## Installation 22 | 23 | The extension can be installed to Chromium based browsers currently, but we plan it to have on Firefox later as well. 24 | 25 | You can build the project by running 26 | ```sh 27 | npm ci 28 | npm run compile 29 | ``` 30 | 31 | commands. If everything went right, then a `dist` folder appeared in the root of the project folder. That folder has to be [added to your browser extensions](chrome://extensions/) as an unpacked extension. You can load unpacked extensions only if you checked in the `Developer mode` (top-right corner). 32 | 33 | ## Manifest V3 Changes 34 | 35 | Since manifest v2 extensions won't be allowed from June 2023, the Swarm Extension now supports manifest v3. But that brings some limitations, not present in v2. Here are the key changes in v3: 36 | 37 | - The `swarm` object won't be injected into dApp pages. Instead each dApp should include the [Swarm Extension Library](library/README.md) into its code to comunicate with the extension. 38 | - Blocking interceptors are not allowed in manifest v3, so the new implementation uses the [Declarative Network Request API](https://developer.chrome.com/docs/extensions/reference/declarativeNetRequest/). This requirement prevents the extension from checking session ID for fake URL requests. That means the extension cannot check the security context of the links that are being accessed. 39 | - If bee URL is set to `localhost`, then fake URL links are redirected to subdomain based bee URLs. For example, trying to access the `bzz://site.eth` URL will result in accessing the `http://site.swarm.localhost:1633/` URL. 40 | 41 | 42 | ## Fake URL 43 | 44 | There is a need to separate dApp context from the user context in order to restrict dApp actions work with keys and resources of the user. 45 | In a web3 environment the browser acts like a backend server. To accomplish this desired role it has to introduce endpoints that dApps can interact with. 46 | These endpoints are called `Fake URLs`. 47 | The naming came from these URLs should not point to any real existing endpoint that a common server could serve, 48 | so the host part of the URL (probably) is not the real destination. 49 | 50 | Web applications can make requests to other decentralized application APIs in the scope of the user 51 | by aiming its corresponding Fake URLs that basically make redirect to these real API address. 52 | It is neccessary, because the targeted services may need additional headers and keys to perform the action that **should not be handled on dApp side**. 53 | The extension can keep these keys and configurations on the side of the user 54 | and it does not expose the secrets to the applications that initialize the call. 55 | In this sense it also works like a `proxy`. 56 | 57 | This architecture also allows changing the default URLs of decentralized services (Bee) to any arbitrary one, 58 | meanwhile dApps do not have to guess this address. 59 | For example Bee client has default `http://127.0.0.1:1633`, user can change it to any other port or even other gateway host, 60 | the dApps will call it in the same way. 61 | 62 | ### Callable Endpoints 63 | 64 | For any action the Fake URL host is `http://swarm.fakeurl.localhost`. 65 | As it is written earlier, it is not the address of the Bee client, 66 | it is just a reserved host address that the extension took and built its Fake URL paths on it. 67 | If the user changes their Bee API address, these endpoints still remain the same from dApp side. 68 | 69 | - `http://swarm.fakeurl.localhost/bzz/*` - `BZZ protocol` redirects to this fake URL, thereby in `*` can be not only content reference with its path, but any BZZ protocol compatible reference. It will be redirected to `bzz` endpoint of the Bee client. 70 | - `http://swarm.fakeurl.localhost/bee-api/*` - it will be forwarded to the Bee API. `*` has to be valid Bee API path 71 | 72 | ## Custom Protocol 73 | 74 | The Swarm protocol to address other P2P content is `bzz`. It makes a redirection to the BZZ endpoint of the Bee node. 75 | If you type `bzz://{content-address}` into the address bar, the page will be redirected to `http(s)://{your-bzz-node-host}/bzz/{content-address}`. This requires the default search engine of the browser to be set to Google. 76 | It also behaves the same on simple google searches on `https://google.com`. 77 | 78 | There will be need for other Swarm specific protocols (or extend the current one), which handle different type of feeds and mutable content. 79 | 80 | You can read about it in more detail in the following section 81 | 82 | ### Swarm HTML 83 | 84 | You can refer to any `bzz` resource in html if you add attribute `is=swarm-X` to your html element, where `X` is the name of the HTML tag element, such as ``. 85 | 86 | Current supported elements: 87 | * `a` -> ` ` `
81 |

Link to other page via BZZ protocol

82 |
Jinn page link 1 85 |
86 |
87 |

Fetch Jinn image via Fake URL with JavaScript

88 | 89 |
90 |
91 |
92 |

Upload a file with BeeJS which is initialized by Fake URL

93 | 94 |
95 |
96 | 97 |

Local Storage handling

98 |
99 |

Local Storage handling in other dApp within iframe

100 | 105 |
106 | 107 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /test/bzz-test-page/index.js: -------------------------------------------------------------------------------- 1 | const swarm = new window.swarm.Swarm() 2 | window.swarmObject = swarm 3 | 4 | const web2Helper = swarm.web2Helper 5 | const postageBatch = swarm.postageBatch 6 | 7 | function echo() { 8 | swarm 9 | .echo('Works') 10 | .then(data => (document.getElementById('echo-placeholder').innerHTML = data)) 11 | .catch(error => (document.getElementById('echo-placeholder').innerHTML = JSON.stringify(error))) 12 | .finally(() => document.getElementById('echo-placeholder').setAttribute('complete', 'true')) 13 | } 14 | 15 | function fetchBeeApiUrl() { 16 | web2Helper 17 | .beeApiUrl() 18 | .then(url => (document.getElementById('bee-api-url-placeholder').innerHTML = url)) 19 | .catch(error => (document.getElementById('bee-api-url-placeholder').innerHTML = JSON.stringify(error))) 20 | .finally(() => document.getElementById('bee-api-url-placeholder').setAttribute('complete', 'true')) 21 | } 22 | 23 | function fetchGlobalPostageBatch() { 24 | const placeholderElement = document.getElementById('global-postage-batch-placeholder') 25 | placeholderElement.setAttribute('complete', 'false') 26 | postageBatch.isGlobalPostageBatchEnabled().then(enabled => { 27 | placeholderElement.innerHTML = enabled 28 | placeholderElement.setAttribute('complete', 'true') 29 | }) 30 | } 31 | 32 | function checkBeeApiAvailable() { 33 | web2Helper.isBeeApiAvailable().then(available => { 34 | const placeholderElement = document.getElementById('bee-api-available-placeholder') 35 | placeholderElement.innerHTML = available 36 | placeholderElement.setAttribute('complete', 'true') 37 | }) 38 | } 39 | 40 | function fetchJinnImage() { 41 | const jinnImageHashElement = document.querySelector('#jinn-image-hash') 42 | 43 | if (!jinnImageHashElement) { 44 | throw new Error('#jinn-image-hash element not found') 45 | } 46 | const bzzContentAddress = `${jinnImageHashElement.value}/images/jinn.png` 47 | const jinnFakeUrl = web2Helper.fakeBzzAddress(bzzContentAddress) 48 | const imageNode = document.createElement('img') 49 | imageNode.src = jinnFakeUrl 50 | imageNode.id = 'fetched-jinn-image' 51 | imageNode.width = 300 52 | document.getElementById('fake-url-fetch-jinn')?.appendChild(imageNode) 53 | } 54 | 55 | async function uploadFileWithBeeJs() { 56 | const beeUrl = web2Helper.fakeBeeApiAddress() 57 | const bee = new window.BeeJs.Bee(beeUrl) 58 | const postageStampAddress = '0000000000000000000000000000000000000000000000000000000000000000' // arbitrary value 59 | const files = [new File(['

Uploaded File through Fake URL!

'], 'index.html', { type: 'text/html' })] 60 | 61 | try { 62 | const hash = await bee.uploadFiles(postageStampAddress, files, { indexDocument: 'index.html' }) 63 | const referenceNode = document.createElement('a') 64 | referenceNode.href = web2Helper.fakeBzzAddress(hash) 65 | referenceNode.innerHTML = hash 66 | document.getElementById('fake-bzz-url-content-1')?.appendChild(referenceNode) 67 | } catch (e) { 68 | console.error('Error happened on file upload (uploadFileWithBeeJs)', e) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/bzz-test-page/jafar-page/images/jinn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethersphere/swarm-extension/d48fb55a82d89f12910fce28730e15f175f8dda0/test/bzz-test-page/jafar-page/images/jinn.png -------------------------------------------------------------------------------- /test/bzz-test-page/jafar-page/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Jafar Test Page 7 | 8 | 9 | 10 | 11 | 12 |
13 | Jafar Test Page 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/bzz-test-page/jinn-page/images/jafar-jinn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethersphere/swarm-extension/d48fb55a82d89f12910fce28730e15f175f8dda0/test/bzz-test-page/jinn-page/images/jafar-jinn.png -------------------------------------------------------------------------------- /test/bzz-test-page/jinn-page/images/jinn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethersphere/swarm-extension/d48fb55a82d89f12910fce28730e15f175f8dda0/test/bzz-test-page/jinn-page/images/jinn.png -------------------------------------------------------------------------------- /test/bzz-test-page/jinn-page/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Jinn Test Page 7 | 8 | 9 | 10 | 11 | 12 |
13 | Jinn Test Page 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/bzz-test-page/local-storage/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Local Storage handling 7 | 8 | 9 | 24 | 25 | 26 | 27 |

Local Storage handling

28 |
29 |

Save data with Swarm localStorage

30 |
31 | 32 |

33 | 34 | 35 |
36 | 39 |
40 |
41 |

Load data with Swarm localStorage

42 |
43 | 44 |

45 | 46 | 47 |
48 | 51 |
52 | 53 | 54 | -------------------------------------------------------------------------------- /test/config/puppeteer_environment.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const os = require('os') 3 | const path = require('path') 4 | const puppeteer = require('puppeteer') 5 | const NodeEnvironment = require('jest-environment-node') 6 | 7 | const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup') 8 | 9 | class PuppeteerEnvironment extends NodeEnvironment { 10 | constructor(config) { 11 | super(config) 12 | } 13 | 14 | async setup() { 15 | await super.setup() 16 | // get the wsEndpoint 17 | const wsEndpoint = fs.readFileSync(path.join(DIR, 'wsEndpoint'), 'utf8') 18 | 19 | if (!wsEndpoint) { 20 | throw new Error('wsEndpoint not found') 21 | } 22 | 23 | // connect to puppeteer 24 | this.global.__BROWSER__ = await puppeteer.connect({ 25 | browserWSEndpoint: wsEndpoint, 26 | }) 27 | } 28 | 29 | async teardown() { 30 | await super.teardown() 31 | } 32 | 33 | runScript(script) { 34 | return super.runScript(script) 35 | } 36 | } 37 | 38 | module.exports = PuppeteerEnvironment 39 | -------------------------------------------------------------------------------- /test/config/setup.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer' 2 | import fs from 'fs' 3 | import os from 'os' 4 | import path from 'path' 5 | 6 | const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup') 7 | const EXTENSION_PATH = path.join(__dirname, '..', '..', '/dist') 8 | 9 | module.exports = async () => { 10 | console.log('Setup Puppeteer') 11 | const browser = await puppeteer.launch({ 12 | executablePath: process.env.PUPPETEER_EXEC_PATH, // set by docker container 13 | headless: false, // extensions only supported in full chrome. 14 | args: [`--disable-extensions-except=${EXTENSION_PATH}`, `--load-extension=${EXTENSION_PATH}`, '--no-sandbox'], 15 | }) 16 | // This global is not available inside tests but only in global teardown 17 | global.__BROWSER__ = browser 18 | // Instead, we expose the connection details via file system to be used in tests 19 | fs.mkdirSync(DIR) 20 | fs.writeFileSync(path.join(DIR, 'wsEndpoint'), browser.wsEndpoint()) 21 | } 22 | -------------------------------------------------------------------------------- /test/config/teardown.ts: -------------------------------------------------------------------------------- 1 | import os from 'os' 2 | import path from 'path' 3 | import rimraf from 'rimraf' 4 | 5 | const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup') 6 | 7 | module.exports = async () => { 8 | // close the browser instance 9 | if (!process.argv.includes('--demo=true')) await global.__BROWSER__.close() 10 | 11 | // clean-up the wsEndpoint file 12 | rimraf.sync(DIR) 13 | } 14 | -------------------------------------------------------------------------------- /test/utils.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 2 | 3 | import { readFile, writeFile } from 'fs' 4 | import { BeeDebug, DebugPostageBatch } from '@ethersphere/bee-js' 5 | import { ElementHandle, Page } from 'puppeteer' 6 | import { DEFAULT_BEE_DEBUG_API_ADDRESS } from '../src/background/constants/addresses' 7 | 8 | /** 9 | * Returns a url for testing the Bee API 10 | */ 11 | export const BEE_DEBUG_API_URL = process.env.BEE_DEBUG_API_URL || DEFAULT_BEE_DEBUG_API_ADDRESS 12 | export const BEE_API_URL = process.env.BEE_API_URL || 'http://127.0.0.1:1633' 13 | 14 | export const BEE_PEER_API_URL = process.env.BEE_PEER_API_URL || 'http://127.0.0.1:11633' 15 | 16 | let extensionId: string 17 | 18 | export async function getExtensionId(): Promise { 19 | if (extensionId) return extensionId 20 | 21 | const page = await global.__BROWSER__.newPage() 22 | await page.goto('chrome://extensions', { waitUntil: 'networkidle0' }) 23 | 24 | await page.waitForSelector('extensions-manager') 25 | 26 | extensionId = await page.evaluate(() => { 27 | const extensionsManager = document.querySelector('extensions-manager') 28 | const extensionsItemList = extensionsManager!.shadowRoot!.querySelector('extensions-item-list') 29 | const extensionsItem = extensionsItemList!.shadowRoot!.querySelectorAll('extensions-item') 30 | const exteinsionItems = Array.from(extensionsItem.entries()) 31 | const extension = exteinsionItems[0][1] 32 | 33 | return extension.id 34 | }) 35 | page.close() 36 | 37 | return extensionId 38 | } 39 | 40 | /** 41 | * It needs to focus on the input element first 42 | */ 43 | export async function replaceInputValue(newValue: string, page: Page): Promise { 44 | await page.keyboard.down('Shift') 45 | await page.keyboard.press('Home') 46 | await page.keyboard.press('Backspace') 47 | await page.keyboard.up('Shift') 48 | await page.keyboard.type(newValue) 49 | } 50 | 51 | export async function getElementBySelector(selector: string, page: Page): Promise> { 52 | await page.waitForSelector(selector) 53 | const element = await page.$(selector) 54 | 55 | if (!element) throw new Error(`Element with selector ${selector} has been not found`) 56 | 57 | return element 58 | } 59 | 60 | export function bzzReferenceByGoogle(contentReference: string): string { 61 | return `https://www.google.com/search?&q=bzz%3A%2F%2F${contentReference}&oq=bzz%3A%2F%2F${contentReference}` 62 | } 63 | 64 | export async function buyStamp(): Promise { 65 | console.log(`Buying stamp on the Bee node ${BEE_DEBUG_API_URL}...`) 66 | const beeDebug = new BeeDebug(BEE_DEBUG_API_URL) 67 | 68 | const batchId = await beeDebug.createPostageBatch('1', 20) 69 | // TODO remove when https://github.com/ethersphere/bee/issues/3300 gets rsolved 70 | await new Promise(resolve => setTimeout(resolve, 200 * 1000)) 71 | let postageBatch: DebugPostageBatch 72 | do { 73 | postageBatch = await beeDebug.getPostageBatch(batchId) 74 | 75 | console.log('Waiting 1 sec for batch ID settlement...') 76 | await sleep() 77 | } while (!postageBatch.usable) 78 | 79 | return batchId 80 | } 81 | 82 | export function getStamp(): string { 83 | if (!process.env.BEE_STAMP) { 84 | throw Error('There is no postage stamp under "BEE_STAMP" environment varaible.') 85 | } 86 | 87 | return process.env.BEE_STAMP 88 | } 89 | 90 | function sleep(ms = 1000) { 91 | return new Promise(resolve => setTimeout(resolve, ms)) 92 | } 93 | 94 | export function loadFile(filePath: string): Promise { 95 | return new Promise((resolve, reject) => { 96 | readFile(filePath, 'utf8', (error, data) => { 97 | if (error) { 98 | return reject(error) 99 | } 100 | 101 | resolve(data) 102 | }) 103 | }) 104 | } 105 | 106 | export function saveFile(filePath: string, data: string): Promise { 107 | return new Promise((resolve, reject) => { 108 | writeFile(filePath, data, 'utf8', error => { 109 | if (error) { 110 | return reject(error) 111 | } 112 | 113 | resolve() 114 | }) 115 | }) 116 | } 117 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "target": "ES6", 5 | "esModuleInterop": true, 6 | "allowSyntheticDefaultImports": true, 7 | "moduleResolution": "node", 8 | "module": "commonjs", // Specify module code generation 9 | "strict": true, 10 | "resolveJsonModule": true, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true, 13 | "jsx": "react", // Support JSX in .tsx files 14 | "skipLibCheck": true, // Skip type checking of all declaration files 15 | }, 16 | "exclude": [ 17 | "dist", 18 | "node_modules" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | import type puppeteer from 'puppeteer' 2 | import type { LocalStorage } from '../src/contentscript/swarm-library/local-storage' 3 | import type { PostageBatch } from '../src/contentscript/swarm-library/postage-batch' 4 | import type { Web2HelperContent } from '../src/contentscript/swarm-library/web2-helper.content' 5 | export {} //indicate it is a module type declaration 6 | 7 | declare global { 8 | namespace NodeJS { 9 | interface Global { 10 | __BROWSER__: puppeteer.Browser 11 | } 12 | } 13 | interface Window { 14 | swarm: { 15 | web2Helper: Web2HelperContent 16 | localStorage: LocalStorage 17 | sessionId: string 18 | bzzLink: typeof import('../src/contentscript/swarm-library/bzz-link') 19 | postageBatch: PostageBatch 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /types/webextension-polyfill/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'webextension-polyfill' 2 | -------------------------------------------------------------------------------- /webpack.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { execSync } from 'child_process' 3 | import CopyPlugin from 'copy-webpack-plugin' 4 | import Path from 'path' 5 | import TerserPlugin from 'terser-webpack-plugin' 6 | import { Compiler, Configuration, DefinePlugin, EnvironmentPlugin, WebpackPluginInstance } from 'webpack' 7 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer' 8 | import { Server } from 'ws' 9 | import PackageJson from './package.json' 10 | 11 | const MAIN_RELOAD_PORT = 16667 12 | const DEPS_RELOAD_PORT = 16668 13 | let liveReloadServer: Server 14 | 15 | interface WebpackEnvParams { 16 | debug: boolean 17 | mode: 'production' | 'development' 18 | fileName: string 19 | /** Build extension's dependencies */ 20 | buildDeps: boolean 21 | } 22 | 23 | const ExtraActionsPlugin = (callback: () => void) => ({ 24 | apply: (compiler: Compiler) => { 25 | compiler.hooks.afterEmit.tap('ExtraActionsPlugin', callback) 26 | }, 27 | }) 28 | 29 | /** 30 | * Send reload signal for the extension on change 31 | * 32 | * @param plugins webpack plugins that the extraAction plugin will be added 33 | * @param dependency if true, main codebase will be compiled on every change, because the main codebase dependent on the given compilation 34 | */ 35 | const addExtraActionPlugin = (plugins: WebpackPluginInstance[], dependency?: boolean) => { 36 | if (liveReloadServer === undefined) return //if liveReloadServer has been not initialized, then it is in production mode 37 | 38 | plugins.push( 39 | ExtraActionsPlugin(() => { 40 | if (dependency) execSync('npm run compile:main') 41 | 42 | liveReloadServer.clients.forEach(client => { 43 | client.send('ping') 44 | }) 45 | }), 46 | ) 47 | } 48 | 49 | const base = (env?: Partial): Configuration => { 50 | const isProduction = env?.mode === 'production' 51 | const filename = env?.fileName || ['index', isProduction ? '.min' : null, '.js'].filter(Boolean).join('') 52 | const entry = Path.resolve(__dirname, 'src') 53 | const path = Path.resolve(__dirname, 'dist') 54 | const target = 'web' 55 | const plugins: WebpackPluginInstance[] = [ 56 | new DefinePlugin({ 57 | 'process.env.ENV': env?.mode || 'development', 58 | 'process.env.IS_WEBPACK_BUILD': 'true', 59 | }), 60 | new CopyPlugin({ 61 | patterns: [ 62 | { 63 | from: 'assets/**/*', 64 | to: path, 65 | }, 66 | { 67 | from: 'manifest.json', 68 | to: path, 69 | }, 70 | ], 71 | }), 72 | ] 73 | addExtraActionPlugin(plugins) 74 | 75 | return { 76 | bail: Boolean(isProduction), 77 | mode: env?.mode || 'development', 78 | devtool: isProduction ? 'source-map' : 'cheap-module-source-map', 79 | entry, 80 | output: { 81 | path, 82 | filename, 83 | sourceMapFilename: filename + '.map', 84 | library: PackageJson.name, 85 | libraryTarget: 'umd', 86 | globalObject: 'this', 87 | }, 88 | module: { 89 | rules: [ 90 | { 91 | test: /\.(ts|js)$/, 92 | // include: entry, 93 | use: { 94 | loader: 'babel-loader', 95 | }, 96 | }, 97 | ], 98 | }, 99 | resolve: { 100 | extensions: ['.ts', '.js'], 101 | fallback: { 102 | path: false, 103 | fs: false, 104 | }, 105 | }, 106 | optimization: { 107 | minimize: isProduction, 108 | minimizer: [ 109 | // This is only used in production mode 110 | new TerserPlugin({ 111 | terserOptions: { 112 | parse: { 113 | // we want terser to parse ecma 8 code. However, we don't want it 114 | // to apply any minfication steps that turns valid ecma 5 code 115 | // into invalid ecma 5 code. This is why the 'compress' and 'output' 116 | // sections only apply transformations that are ecma 5 safe 117 | // https://github.com/facebook/create-react-app/pull/4234 118 | ecma: 2018, 119 | }, 120 | compress: { 121 | ecma: 5, 122 | }, 123 | mangle: { 124 | safari10: true, 125 | }, 126 | output: { 127 | ecma: 5, 128 | comments: false, 129 | }, 130 | }, 131 | // Use multi-process parallel running to improve the build speed 132 | // Default number of concurrent runs: os.cpus().length - 1 133 | parallel: true, 134 | }), 135 | ], 136 | }, 137 | plugins, 138 | target, 139 | node: { 140 | global: true, 141 | __filename: 'mock', 142 | __dirname: 'mock', 143 | }, 144 | performance: { 145 | hints: false, 146 | }, 147 | } 148 | } 149 | 150 | const background = (env?: Partial): Configuration => { 151 | const isProduction = env?.mode === 'production' 152 | const filename = 'background.js' 153 | const entry = Path.resolve(__dirname, 'src', 'background') 154 | const path = Path.resolve(__dirname, 'dist') 155 | const target = 'web' 156 | const plugins: WebpackPluginInstance[] = [ 157 | new DefinePlugin({ 158 | 'process.env.ENV': env?.mode || 'development', 159 | 'process.env.IS_WEBPACK_BUILD': 'true', 160 | }), 161 | new EnvironmentPlugin({ 162 | SWARM_DEVELOPMENT: !isProduction, 163 | }), 164 | ] 165 | addExtraActionPlugin(plugins) 166 | 167 | return { 168 | bail: Boolean(isProduction), 169 | mode: env?.mode || 'development', 170 | devtool: isProduction ? 'source-map' : 'cheap-module-source-map', 171 | entry, 172 | output: { 173 | path, 174 | filename, 175 | sourceMapFilename: filename + '.map', 176 | library: PackageJson.name, 177 | libraryTarget: 'umd', 178 | globalObject: 'this', 179 | }, 180 | module: { 181 | rules: [ 182 | { 183 | test: /\.(ts|js)$/, 184 | // include: entry, 185 | use: { 186 | loader: 'babel-loader', 187 | }, 188 | }, 189 | ], 190 | }, 191 | resolve: { 192 | extensions: ['.ts', '.js'], 193 | fallback: { 194 | path: false, 195 | fs: false, 196 | }, 197 | }, 198 | optimization: { 199 | minimize: isProduction, 200 | minimizer: [ 201 | // This is only used in production mode 202 | new TerserPlugin({ 203 | terserOptions: { 204 | parse: { 205 | // we want terser to parse ecma 8 code. However, we don't want it 206 | // to apply any minfication steps that turns valid ecma 5 code 207 | // into invalid ecma 5 code. This is why the 'compress' and 'output' 208 | // sections only apply transformations that are ecma 5 safe 209 | // https://github.com/facebook/create-react-app/pull/4234 210 | ecma: 2018, 211 | }, 212 | compress: { 213 | ecma: 5, 214 | }, 215 | mangle: { 216 | safari10: true, 217 | }, 218 | output: { 219 | ecma: 5, 220 | comments: false, 221 | }, 222 | }, 223 | // Use multi-process parallel running to improve the build speed 224 | // Default number of concurrent runs: os.cpus().length - 1 225 | parallel: true, 226 | }), 227 | ], 228 | }, 229 | plugins, 230 | target, 231 | node: { 232 | global: true, 233 | __filename: 'mock', 234 | __dirname: 'mock', 235 | }, 236 | performance: { 237 | hints: false, 238 | }, 239 | } 240 | } 241 | 242 | const contentscript = ( 243 | scriptType: 'document-start' | 'swarm-library' | 'swarm-html', 244 | env?: Partial, 245 | ): Configuration => { 246 | const dependencies = ['swarm-library', 'swarm-html'] 247 | const isProduction = env?.mode === 'production' 248 | const filename = `${scriptType}.js` 249 | const entry = Path.resolve(__dirname, 'src', 'contentscript', scriptType) 250 | const path = Path.resolve(__dirname, 'dist') 251 | const target = 'web' 252 | const plugins: WebpackPluginInstance[] = [ 253 | new DefinePlugin({ 254 | 'process.env.ENV': env?.mode || 'development', 255 | 'process.env.IS_WEBPACK_BUILD': 'true', 256 | }), 257 | ] 258 | addExtraActionPlugin(plugins, dependencies.includes(scriptType)) 259 | 260 | return { 261 | bail: Boolean(isProduction), 262 | mode: env?.mode || 'development', 263 | devtool: isProduction ? 'source-map' : 'cheap-module-source-map', 264 | entry, 265 | output: { 266 | path, 267 | filename, 268 | sourceMapFilename: filename + '.map', 269 | globalObject: 'this', 270 | }, 271 | module: { 272 | rules: [ 273 | { 274 | test: /(?): Configuration => { 344 | const isProduction = env?.mode === 'production' 345 | const filename = 'index.js' 346 | const entry = Path.resolve(__dirname, 'src', 'popup-page', 'index.tsx') 347 | const path = Path.resolve(__dirname, 'dist', 'popup-page') 348 | const assetsPath = Path.resolve(path, 'assets') 349 | const target = 'web' 350 | const plugins: WebpackPluginInstance[] = [ 351 | new DefinePlugin({ 352 | 'process.env.ENV': env?.mode || 'development', 353 | 'process.env.IS_WEBPACK_BUILD': 'true', 354 | }), 355 | new CopyPlugin({ 356 | patterns: [ 357 | { 358 | from: 'assets/**/*', 359 | to: assetsPath, 360 | }, 361 | { 362 | from: 'src/popup-page/index.html', 363 | to: () => { 364 | return `${path}/[name].[ext]` 365 | }, 366 | }, 367 | ], 368 | }), 369 | ] 370 | 371 | addExtraActionPlugin(plugins) 372 | 373 | return { 374 | bail: Boolean(isProduction), 375 | mode: env?.mode || 'development', 376 | devtool: isProduction ? 'source-map' : 'cheap-module-source-map', 377 | entry, 378 | output: { 379 | path, 380 | filename, 381 | sourceMapFilename: filename + '.map', 382 | library: PackageJson.name, 383 | libraryTarget: 'umd', 384 | globalObject: 'this', 385 | }, 386 | module: { 387 | rules: [ 388 | { 389 | test: /\.(tsx|ts|jsx|js)$/, 390 | // include: entry, 391 | use: { 392 | loader: 'babel-loader', 393 | }, 394 | }, 395 | { 396 | test: /\.css$/, 397 | use: ['style-loader', 'css-loader'], 398 | }, 399 | { 400 | test: /\.(png|jp(e*)g|svg|gif|ttf)$/, 401 | use: ['file-loader'], 402 | }, 403 | ], 404 | }, 405 | resolve: { 406 | extensions: ['.tsx', '.ts', '.js', '.jsx'], 407 | fallback: { 408 | path: false, 409 | fs: false, 410 | }, 411 | alias: { 412 | react: Path.join(__dirname, 'node_modules', 'react'), 413 | 'react-dom': Path.join(__dirname, 'node_modules', 'react-dom'), 414 | }, 415 | }, 416 | optimization: { 417 | minimize: isProduction, 418 | minimizer: [ 419 | // This is only used in production mode 420 | new TerserPlugin({ 421 | terserOptions: { 422 | parse: { 423 | // we want terser to parse ecma 8 code. However, we don't want it 424 | // to apply any minfication steps that turns valid ecma 5 code 425 | // into invalid ecma 5 code. This is why the 'compress' and 'output' 426 | // sections only apply transformations that are ecma 5 safe 427 | // https://github.com/facebook/create-react-app/pull/4234 428 | ecma: 2018, 429 | }, 430 | compress: { 431 | ecma: 5, 432 | }, 433 | mangle: { 434 | safari10: true, 435 | }, 436 | output: { 437 | ecma: 5, 438 | comments: false, 439 | }, 440 | }, 441 | // Use multi-process parallel running to improve the build speed 442 | // Default number of concurrent runs: os.cpus().length - 1 443 | parallel: true, 444 | }), 445 | ], 446 | }, 447 | plugins, 448 | target, 449 | node: { 450 | global: true, 451 | __filename: 'mock', 452 | __dirname: 'mock', 453 | }, 454 | performance: { 455 | hints: false, 456 | }, 457 | } 458 | } 459 | 460 | export default (env?: Partial): Configuration[] => { 461 | let baseConfig: Configuration[] 462 | 463 | // if environment node is undefined then it always build in production mode 464 | if (!env?.mode) { 465 | env = { ...env, mode: 'production' } 466 | } 467 | 468 | // eslint-disable-next-line no-console 469 | console.log('env', env) 470 | 471 | if (env?.buildDeps) { 472 | if (env?.mode === 'development') liveReloadServer = new Server({ port: DEPS_RELOAD_PORT }) 473 | 474 | baseConfig = [contentscript('document-start', env)] 475 | } else { 476 | if (env?.mode === 'development') liveReloadServer = new Server({ port: MAIN_RELOAD_PORT }) 477 | 478 | baseConfig = [base(env), background(env), contentscript('document-start', env), popupPage(env)] 479 | } 480 | 481 | if (env?.debug) { 482 | baseConfig.forEach(config => { 483 | if (config.plugins) config.plugins.push(new BundleAnalyzerPlugin()) 484 | else config.plugins = [new BundleAnalyzerPlugin()] 485 | config.profile = true 486 | }) 487 | } 488 | 489 | return baseConfig 490 | } 491 | --------------------------------------------------------------------------------