Repository: MetaMask/metamask-onboarding Branch: main Commit: 1599fd9a75b6 Files: 16 Total size: 19.9 KB Directory structure: gitextract_hys0e41c/ ├── .editorconfig ├── .eslintrc.js ├── .gitattributes ├── .github/ │ ├── CODEOWNERS │ └── workflows/ │ ├── build-lint.yml │ └── security-code-scanner.yml ├── .gitignore ├── .nvmrc ├── .yarnrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json ├── rollup.config.js ├── src/ │ └── index.ts └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true ================================================ FILE: .eslintrc.js ================================================ module.exports = { extends: ['@metamask/eslint-config'], overrides: [ { files: ['*.js'], extends: ['@metamask/eslint-config-nodejs'], }, { files: ['rollup.config.js'], parserOptions: { sourceType: 'module', }, }, { files: ['src/**/*.ts'], env: { browser: true, }, }, { files: ['*.ts'], extends: ['@metamask/eslint-config-typescript'], }, ], ignorePatterns: ['!.eslintrc.js', '!.prettierrc.js', 'dist'], }; ================================================ FILE: .gitattributes ================================================ * text=auto # Reviewing the lockfile contents is an important step in verifying that # we're using the dependencies we expect to be using yarn.lock linguist-generated=false ================================================ FILE: .github/CODEOWNERS ================================================ # Lines starting with '#' are comments. # Each line is a file pattern followed by one or more owners. * @MetaMask/devs ================================================ FILE: .github/workflows/build-lint.yml ================================================ name: Build and Lint on: push: branches: [main] pull_request: jobs: build-lint-test: name: Build and Lint runs-on: ubuntu-20.04 strategy: matrix: node-version: [12.x, 14.x, 16.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} - name: Get Yarn cache directory run: echo "::set-output name=YARN_CACHE_DIR::$(yarn cache dir)" id: yarn-cache-dir - name: Get Yarn version run: echo "::set-output name=YARN_VERSION::$(yarn --version)" id: yarn-version - name: Cache yarn dependencies uses: actions/cache@v2 with: path: ${{ steps.yarn-cache-dir.outputs.YARN_CACHE_DIR }} key: yarn-cache-${{ runner.os }}-${{ steps.yarn-version.outputs.YARN_VERSION }}-${{ hashFiles('yarn.lock') }} - run: yarn --frozen-lockfile - run: yarn allow-scripts - run: yarn build - run: yarn lint - name: Validate RC changelog if: ${{ startsWith(github.ref, 'release-v') }} run: yarn auto-changelog validate --rc - name: Validate changelog if: ${{ !startsWith(github.ref, 'release-v') }} run: yarn auto-changelog validate all-jobs-pass: name: All jobs pass runs-on: ubuntu-20.04 needs: - build-lint-test steps: - run: echo "Great success!" ================================================ FILE: .github/workflows/security-code-scanner.yml ================================================ name: MetaMask Security Code Scanner on: push: branches: - main pull_request: branches: - main workflow_dispatch: jobs: run-security-scan: runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write steps: - name: MetaMask Security Code Scanner uses: MetaMask/Security-Code-Scanner@main with: repo: ${{ github.repository }} paths_ignored: | .storybook/ '**/__snapshots__/' '**/*.snap' '**/*.stories.js' '**/*.stories.tsx' '**/*.test.browser.ts*' '**/*.test.js*' '**/*.test.ts*' '**/fixtures/' '**/jest.config.js' '**/jest.environment.js' '**/mocks/' '**/test*/' docs/ e2e/ merged-packages/ node_modules storybook/ test*/ rules_excluded: example project_metrics_token: ${{ secrets.SECURITY_SCAN_METRICS_TOKEN }} slack_webhook: ${{ secrets.APPSEC_BOT_SLACK_WEBHOOK }} ================================================ FILE: .gitignore ================================================ # Yarn /node_modules /yarn-error.log # builds *.tgz /dist # Built JS /src/*.d.ts /src/*.js /src/*.map # ESLint .eslintcache ================================================ FILE: .nvmrc ================================================ v12 ================================================ FILE: .yarnrc ================================================ ignore-scripts true ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [1.0.1] ### Changed - Update various dependencies ([#49](https://github.com/MetaMask/metamask-onboarding/pull/49), [#68](https://github.com/MetaMask/metamask-onboarding/pull/68), [#71](https://github.com/MetaMask/metamask-onboarding/pull/71), [#60](https://github.com/MetaMask/metamask-onboarding/pull/60), [#61](https://github.com/MetaMask/metamask-onboarding/pull/61)) - Use `@lavamoat/allow-scripts` for improved security ([#67](https://github.com/MetaMask/metamask-onboarding/pull/67)) - Remove unused dependencies ([#66](https://github.com/MetaMask/metamask-onboarding/pull/66)) - Switch from CircleCI to GitHub Actions ([#64](https://github.com/MetaMask/metamask-onboarding/pull/64)) - Update Node.js used for CI from v10 to v12 ([#59](https://github.com/MetaMask/metamask-onboarding/pull/59), [#73](https://github.com/MetaMask/metamask-onboarding/pull/73)) - Refactor to improve readability ([#45](https://github.com/MetaMask/metamask-onboarding/pull/45), [#47](https://github.com/MetaMask/metamask-onboarding/pull/47), [#48](https://github.com/MetaMask/metamask-onboarding/pull/48)) ### Fixed - Fix import of `bowser` package ([#57](https://github.com/MetaMask/metamask-onboarding/pull/57), [#63](https://github.com/MetaMask/metamask-onboarding/pull/63)) ## [1.0.0] - 2020-07-02 ### Changed - **BREAKING**: Rename export to `MetaMaskOnboarding` (#32) - Update example in README with validated HTML (#30) ### Fixed - Use Firefox URL without specified language (#38) [Unreleased]: https://github.com/MetaMask/metamask-onboarding/compare/v1.0.1...HEAD [1.0.1]: https://github.com/MetaMask/metamask-onboarding/compare/v1.0.0...v1.0.1 [1.0.0]: https://github.com/MetaMask/metamask-onboarding/releases/tag/v1.0.0 ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 MetaMask Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # MetaMask Onboarding This library is used to help onboard new MetaMask users. It allows you to ask the MetaMask extension to redirect users back to your page after onboarding has finished. This library will register the current page as having initiated onboarding, so that MetaMask knows where to redirect the user after onboarding. Note that the page will be automatically reloaded a single time once a MetaMask installation is detected, in order to facilitate this registration. ## Installation `@metamask/onboarding` is made available as either a CommonJS module, and ES6 module, or an ES5 bundle. - ES6 module: `import MetaMaskOnboarding from '@metamask/onboarding'` - ES5 module: `const MetaMaskOnboarding = require('@metamask/onboarding')` - ES5 bundle: `dist/metamask-onboarding.bundle.js` (this can be included directly in a page) ## Usage [See _§ Onboarding Library_ on the MetaMask Docs website for examples.](https://docs.metamask.io/guide/onboarding-library.html) ## API Assuming `import MetaMaskOnboarding from '@metamask/onboarding'`, the following API is available. ### Static methods #### `MetaMaskOnboarding.isMetaMaskInstalled()` Returns `true` if a MetaMask-like provider is detected, or `false` otherwise. Note that we don't provide any guarantee that this is correct, as non-MetaMask wallets can disguise themselves as MetaMask. ### Static properties #### `MetaMaskOnboarding.FORWARDER_MODE` A set of constants for each of the available forwarder modes. | Constant | Description | | :--------- | :------------------------------------------------------------------------------------------------------------------------------------- | | `INJECT` | Inject a `iframe` to that will refresh until MetaMask has installed | | `OPEN_TAB` | Open a tab to a new page that will refresh until MetaMask has installed—this is only useful if the client app has disallowed `iframes` | ### Constructor #### `new MetaMaskOnboarding()` The constructor accepts an optional options bag with the following: | Option | Description | | :---------------- | :---------------------------------------------------------------------------------------------------------- | | `forwarderOrigin` | Override the forwarder URL, useful for testing. **Optional**, defaults to `'https://fwd.metamask.io'`. | | `forwarderMode` | One of the available forwarder modes. **Optional**, defaults to `MetaMaskOnboarding.FORWARDER_MODE.INJECT`. | ### Instance methods #### `startOnboarding()` Starts onboarding by opening the MetaMask download page and waiting for MetaMask to be installed. Once the MetaMask extension installation is detected, a message will be sent to MetaMask to register the current site as the onboarding initiator. #### `stopOnboarding()` Stops onboarding registration, including removing the injected `iframe` (if any). ## Contributing ### Setup - Install [Node.js](https://nodejs.org) version 12 - If you are using [nvm](https://github.com/creationix/nvm#installation) (recommended) running `nvm use` will automatically choose the right node version for you. - Install [Yarn v1](https://yarnpkg.com/en/docs/install) - Run `yarn setup` to install dependencies and run any requried post-install scripts - **Warning:** Do not use the `yarn` / `yarn install` command directly. Use `yarn setup` instead. The normal install command will skip required post-install scripts, leaving your development environment in an invalid state. ### Linting Run `yarn lint` to run the linter. ### Release & Publishing The project follows the same release process as the other libraries in the MetaMask organization: 1. Create a release branch - For a typical release, this would be based on `main` - To update an older maintained major version, base the release branch on the major version branch (e.g. `1.x`) 2. Update the changelog 3. Update version in package.json file (e.g. `yarn version --minor --no-git-tag-version`) 4. Create a pull request targeting the base branch (e.g. `main` or `1.x`) 5. Code review and QA 6. Once approved, the PR is squashed & merged 7. The commit on the base branch is tagged 8. The tag can be published as needed ## License This project is available under the [MIT license](./LICENSE). ================================================ FILE: package.json ================================================ { "name": "@metamask/onboarding", "version": "1.0.1", "description": "Assists with onboarding new MetaMask users", "main": "dist/metamask-onboarding.cjs.js", "module": "dist/metamask-onboarding.es.js", "types": "dist/index.d.ts", "homepage": "https://github.com/MetaMask/metamask-onboarding#readme", "bugs": { "url": "https://github.com/MetaMask/metamask-onboarding/issues" }, "repository": { "type": "git", "url": "https://github.com/MetaMask/metamask-onboarding.git" }, "publishConfig": { "registry": "https://registry.npmjs.org/", "access": "public" }, "license": "MIT", "files": [ "/dist" ], "scripts": { "setup": "yarn install && yarn allow-scripts", "prepublishOnly": "yarn build", "lint:eslint": "eslint . --cache --ext js,ts", "lint:misc": "prettier '**/*.json' '**/*.md' '!CHANGELOG.md' '**/*.yml' --ignore-path .gitignore", "lint": "yarn lint:eslint && yarn lint:misc --check", "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write", "build": "rollup --config" }, "dependencies": { "bowser": "^2.9.0" }, "devDependencies": { "@lavamoat/allow-scripts": "^1.0.6", "@metamask/auto-changelog": "^2.3.0", "@metamask/eslint-config": "^6.0.0", "@metamask/eslint-config-nodejs": "^6.0.0", "@metamask/eslint-config-typescript": "^6.0.0", "@rollup/plugin-node-resolve": "^7.1.1", "@typescript-eslint/eslint-plugin": "^4.26.0", "@typescript-eslint/parser": "^4.26.0", "eslint": "^7.27.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.23.4", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^3.4.0", "prettier": "^2.3.0", "rollup": "^2.18.0", "rollup-plugin-typescript2": "^0.30.0", "typescript": "^4.3.2" }, "lavamoat": { "allowScripts": { "@lavamoat/preinstall-always-fail": false } } } ================================================ FILE: rollup.config.js ================================================ import typescript from 'rollup-plugin-typescript2'; import resolve from '@rollup/plugin-node-resolve'; const config = [ { external: ['bowser'], input: 'src/index.ts', output: [ { file: 'dist/metamask-onboarding.cjs.js', format: 'cjs', sourcemap: true, }, { file: 'dist/metamask-onboarding.es.js', format: 'es', sourcemap: true, }, ], plugins: [typescript()], }, { input: 'src/index.ts', output: [ { file: 'dist/metamask-onboarding.bundle.js', format: 'iife', name: 'MetaMaskOnboarding', }, ], plugins: [typescript(), resolve()], }, ]; export default config; ================================================ FILE: src/index.ts ================================================ import Bowser from 'bowser'; const ONBOARDING_STATE = { INSTALLED: 'INSTALLED' as const, NOT_INSTALLED: 'NOT_INSTALLED' as const, REGISTERED: 'REGISTERED' as const, REGISTERING: 'REGISTERING' as const, RELOADING: 'RELOADING' as const, }; const EXTENSION_DOWNLOAD_URL = { CHROME: 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn', FIREFOX: 'https://addons.mozilla.org/firefox/addon/ether-metamask/', DEFAULT: 'https://metamask.io', }; // sessionStorage key const REGISTRATION_IN_PROGRESS = 'REGISTRATION_IN_PROGRESS'; // forwarder iframe id const FORWARDER_ID = 'FORWARDER_ID'; export default class Onboarding { static FORWARDER_MODE = { INJECT: 'INJECT' as const, OPEN_TAB: 'OPEN_TAB' as const, }; private readonly forwarderOrigin: string; private readonly downloadUrl: string; private readonly forwarderMode: keyof typeof Onboarding.FORWARDER_MODE; private state: keyof typeof ONBOARDING_STATE; constructor({ forwarderOrigin = 'https://fwd.metamask.io', forwarderMode = Onboarding.FORWARDER_MODE.INJECT, } = {}) { this.forwarderOrigin = forwarderOrigin; this.forwarderMode = forwarderMode; this.state = Onboarding.isMetaMaskInstalled() ? ONBOARDING_STATE.INSTALLED : ONBOARDING_STATE.NOT_INSTALLED; const browser = Onboarding._detectBrowser(); if (browser) { this.downloadUrl = EXTENSION_DOWNLOAD_URL[browser]; } else { this.downloadUrl = EXTENSION_DOWNLOAD_URL.DEFAULT; } this._onMessage = this._onMessage.bind(this); this._onMessageFromForwarder = this._onMessageFromForwarder.bind(this); this._openForwarder = this._openForwarder.bind(this); this._openDownloadPage = this._openDownloadPage.bind(this); this.startOnboarding = this.startOnboarding.bind(this); this.stopOnboarding = this.stopOnboarding.bind(this); window.addEventListener('message', this._onMessage); if ( forwarderMode === Onboarding.FORWARDER_MODE.INJECT && sessionStorage.getItem(REGISTRATION_IN_PROGRESS) === 'true' ) { Onboarding._injectForwarder(this.forwarderOrigin); } } _onMessage(event: MessageEvent) { if (event.origin !== this.forwarderOrigin) { // Ignoring non-forwarder message return undefined; } if (event.data.type === 'metamask:reload') { return this._onMessageFromForwarder(event); } console.debug( `Unknown message from '${event.origin}' with data ${JSON.stringify( event.data, )}`, ); return undefined; } _onMessageUnknownStateError(state: never): never { throw new Error(`Unknown state: '${state}'`); } async _onMessageFromForwarder(event: MessageEvent) { switch (this.state) { case ONBOARDING_STATE.RELOADING: console.debug('Ignoring message while reloading'); break; case ONBOARDING_STATE.NOT_INSTALLED: console.debug('Reloading now to register with MetaMask'); this.state = ONBOARDING_STATE.RELOADING; location.reload(); break; case ONBOARDING_STATE.INSTALLED: console.debug('Registering with MetaMask'); this.state = ONBOARDING_STATE.REGISTERING; await Onboarding._register(); this.state = ONBOARDING_STATE.REGISTERED; (event.source as Window).postMessage( { type: 'metamask:registrationCompleted' }, event.origin, ); this.stopOnboarding(); break; case ONBOARDING_STATE.REGISTERING: console.debug('Already registering - ignoring reload message'); break; case ONBOARDING_STATE.REGISTERED: console.debug('Already registered - ignoring reload message'); break; default: this._onMessageUnknownStateError(this.state); } } /** * Starts onboarding by opening the MetaMask download page and the Onboarding forwarder */ startOnboarding() { sessionStorage.setItem(REGISTRATION_IN_PROGRESS, 'true'); this._openDownloadPage(); this._openForwarder(); } /** * Stops onboarding registration, including removing the injected forwarder (if any) * * Typically this function is not necessary, but it can be useful for cases where * onboarding completes before the forwarder has registered. */ stopOnboarding() { if (sessionStorage.getItem(REGISTRATION_IN_PROGRESS) === 'true') { if (this.forwarderMode === Onboarding.FORWARDER_MODE.INJECT) { console.debug('Removing forwarder'); Onboarding._removeForwarder(); } sessionStorage.setItem(REGISTRATION_IN_PROGRESS, 'false'); } } _openForwarder() { if (this.forwarderMode === Onboarding.FORWARDER_MODE.OPEN_TAB) { window.open(this.forwarderOrigin, '_blank'); } else { Onboarding._injectForwarder(this.forwarderOrigin); } } _openDownloadPage() { window.open(this.downloadUrl, '_blank'); } /** * Checks whether the MetaMask extension is installed */ static isMetaMaskInstalled() { return Boolean( (window as any).ethereum && (window as any).ethereum.isMetaMask, ); } static _register() { return (window as any).ethereum.request({ method: 'wallet_registerOnboarding', }); } static _injectForwarder(forwarderOrigin: string) { const container = document.body; const iframe = document.createElement('iframe'); iframe.setAttribute('height', '0'); iframe.setAttribute('width', '0'); iframe.setAttribute('style', 'display: none;'); iframe.setAttribute('src', forwarderOrigin); iframe.setAttribute('id', FORWARDER_ID); container.insertBefore(iframe, container.children[0]); } static _removeForwarder() { document.getElementById(FORWARDER_ID)?.remove(); } static _detectBrowser() { const browserInfo = Bowser.parse(window.navigator.userAgent); if (browserInfo.browser.name === 'Firefox') { return 'FIREFOX'; } else if ( ['Chrome', 'Chromium'].includes(browserInfo.browser.name || '') ) { return 'CHROME'; } return null; } } ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "declaration": true, "esModuleInterop": true, "module": "ES6", "moduleResolution": "Node", "sourceMap": true, "strict": true, "target": "es5", "lib": ["DOM", "ES2016"] }, "include": ["./**/*.ts"] }