text:
version: "3.9"
services:
  firefox:
    image: ${FIREFOX_IMAGE:-selenium/standalone-firefox}:${FIREFOX_VERSION:-latest}
    shm_size: 2g
    ports:
      - 4444
      - 7900
  chromium:
    # WARN: `standalone-chrome` does NOT work on ARM-based machines;
    #  see https://github.com/SeleniumHQ/docker-selenium#experimental-mult-arch-aarch64armhfamd64-images;
    #  try using `seleniarm/standalone-chromium` instead
    # export CHROMIUM_IMAGE=seleniarm/standalone-chromium
    image: ${CHROMIUM_IMAGE:-selenium/standalone-chrome}:${CHROMIUM_VERSION:-latest}
    shm_size: 2g
    ports:
      - 4444
      - 7900
  kubo:
    image: ipfs/kubo:${KUBO_VERSION:-latest}
    ports:
      - 4001
      - 5001
      - 8080
    volumes:
      - ./ci/access-control-allow-all.sh:/container-init.d/001-access-control-allow-all.sh
  e2e:
    build:
      dockerfile: ./Dockerfile
      context: .
    environment:
      - SELENIUM_REMOTE_CHROMIUM_URL=http://chromium:4444
      - SELENIUM_REMOTE_FIREFOX_URL=http://firefox:4444
      - IPFS_API_URL=http://kubo:5001
      - CUSTOM_GATEWAY_URL=http://kubo:8080
      - TEST_E2E=true
      - TEST_HEADLESS=${TEST_HEADLESS:-false}
      - IPFS_COMPANION_VERSION=${IPFS_COMPANION_VERSION}
    volumes:
      - ./build:/home/node/app/build


text:
# Security notes

The IPFS protocol and its implementations are still in heavy development. This means that there may be problems in our protocols, or there may be mistakes in our implementations. And — though IPFS is not production-ready yet — many people are already running nodes on their machines, so we take security vulnerabilities very seriously.

## Reporting security issues

If you discover a security issue in ipfs-companion, please bring it to our attention right away!

If you find a vulnerability that may affect live deployments — for example, by exposing a remote execution exploit — please send your report privately to security@ipfs.io **Please do not file a public issue.**

If the issue is a protocol weakness that cannot be immediately exploited, or something not yet deployed, just discuss it openly.

## Local build and source verification

Security-conscious users can confirm that downloaded package does match source code.

Required steps:

1. Download package version that is to be verified
2. Check out sources of the [same tag](https://github.com/ipfs/ipfs-companion/tags)
2. Build package from sources using `yarn dev-build` command.
   As a result, you will have freshly built packages in `build/` directory
3. Unzip contents and compare manually or use handy one-liners below.

Convention used in examples below:
- `ipfs-firefox-addon1.xpi` and `ipfs-firefox-addon2.xpi` are equal and match Git sources
-  `ipfs-firefox-addon3.xpi` contains changes not present in current sources

### Compare SHA-256

Note that XPIs are unzipped before being piped to `sha256sum`:
```bash
$ diff -q <(unzip -p  ipfs-firefox-addon1.xpi|sha256sum|cut -f1 -d' ') <(unzip -p  ipfs-firefox-addon3.xpi|sha256sum|cut -f1 -d' ') && echo same || echo not_same
```

Sample output for files with matching contents:
```bash
$ diff <(unzip -v -l ipfs-firefox-addon1.xpi | cut -c 1-9,59-,49-57 | sort -k3) <(unzip -v -l ipfs-firefox-addon2.xpi | cut -c 1-9,59-,49-57 | sort -k3)  && echo same || echo not_same
same

```

Sample output for files with different contents:
```bash
$ diff -q <(unzip -p  ipfs-firefox-addon1.xpi|sha256sum|cut -f1 -d' ') <(unzip -p  ipfs-firefox-addon3.xpi|sha256sum|cut -f1 -d' ') && echo same || echo not_same
Files /proc/self/fd/11 and /proc/self/fd/12 differ
not_same
```

### Inspect differences

It is possible to list package contents with CRC-32 checksums provided by ZIP container:
```bash
$ unzip -v -l ipfs-firefox-addon1.xpi | cut -c 1-9,59-,49-57 | sort -k3
```
To compare two packages in one command:
```bash
$ diff <(unzip -v -l ipfs-firefox-addon1.xpi | cut -c 1-9,59-,49-57 | sort -k3) <(unzip -v -l ipfs-firefox-addon3.xpi | cut -c 1-9,59-,49-57 | sort -k3) && echo same || echo not_same
```

Sample output for two different versions of XPI:
```diff
$ diff <(unzip -v -l ipfs-firefox-addon1.xpi | cut -c 1-9,59-,49-57 | sort -k3) <(unzip -v -l ipfs-firefox-addon3.xpi | cut -c 1-9,59-,49-57 | sort -k3) && echo same || echo not_same
5,7c5,7
<      174 d991ba90 defaults/preferences/prefs.js
<    37615          30 files
<     2270 06d511c2 harness-options.json
---
>      225 5b287a38 defaults/preferences/prefs.js
>    38998          30 files
>     2315 6291e716 harness-options.json
13c13
<      385 c2c13711 options.xul
---
>      513 4f3e0732 options.xul
33,34c33,34
<     6376 45297f28 resources/ipfs-firefox-addon/lib/index.js
<      966 e48f0540 resources/ipfs-firefox-addon/lib/package.json
---
>     7517 7565d9af resources/ipfs-firefox-addon/lib/index.js
>      984 83079aef resources/ipfs-firefox-addon/lib/package.json
not_same
```

repo_name: ipfs/ipfs-companion

repo_language: JavaScript

mime_type: text/x-diff


ipfs-companion

type: opencollective

pbjs: bin/pbjs

pbts: bin/pbts

adbkit: bin/adbkit

deprecated: This version has been deprecated and is no longer supported or maintained

type: github

type: individual

acorn: bin/acorn

addons-linter: bin/addons-linter

type: tidelift

type: patreon

type: consulting

bunyan: bin/bunyan

deprecated: This module has been superseded by the multiformats module

cssesc: bin/cssesc

esbuild: bin/esbuild

resolve: bin/resolve

type: paypal

deprecated: Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau

deprecated: Use mz or fs-extra^3.0 with Promise Support

fx-runner: bin/fx-runner

which: bin/which

get-firefox: bin/get-firefox

semver: bin/semver

deprecated: this library is no longer supported

he: bin/he

@achingbrain/node-fetch

license: MIT

jsesc: bin/jsesc

ncp: bin/ncp

deprecated: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.

regjsparser: bin/parser

deprecated: request has been deprecated, see https://github.com/request/request/issues/3142

deprecated: Please upgrade to v7.0.2+ of superagent.  We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing.  See the releases tab for more information at <https://github.com/visionmedia/superagent/releases>.

deprecated: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.


text:
# Licensed under MIT.
# Copyright (2016) by Kevin van Zonneveld https://twitter.com/kvz
#
# https://www.npmjs.com/package/fakefile
#
# Please do not edit this file directly, but propose changed upstream instead:
# https://github.com/kvz/fakefile/blob/master/Makefile
#
# This Makefile offers convience shortcuts into any Node.js project that utilizes npm scripts.
# It functions as a wrapper around the actual listed in `package.json`
# So instead of typing:
#
#  $ npm script build:assets
#
# you could also type:
#
#  $ make build-assets
#
# Notice that colons (:) are replaced by dashes for Makefile compatibility.
#
# The benefits of this wrapper are:
#
# - You get to keep the the scripts package.json, which is more portable
#   (Makefiles & Windows are harder to mix)
# - Offer a polite way into the project for developers coming from different
#   languages (npm scripts is obviously very Node centric)
# - Profit from better autocomplete (make <TAB><TAB>) than npm currently offers.
#   OSX users will have to install bash-completion
#   (http://davidalger.com/development/bash-completion-on-os-x-with-brew/)

ifeq ($(shell test -e ./yarn.lock && echo -n yes),yes)
	RUNNER=yarn
	INSTALLER=yarn install
else
	RUNNER=npm run
	INSTALLER=npm install
endif

define npm_script_targets
TARGETS := $(shell \
	node -e 'for (var k in require("./package.json").scripts) {console.log(k.replace(/:/g, "-"));}'
		| grep -v -E "^install$$"
)
$$(TARGETS):
	$(RUNNER) $(shell \
							node -e 'for (var k in require("./package.json").scripts) {console.log(k.replace(/:/g, "-"), k);}'
								| grep -E "^$(MAKECMDGOALS)\s"
								| head -n1
								| awk '{print $$2}'
							)

.PHONY: $$(TARGETS)
endef

$(eval $(call npm_script_targets))

# These npm run scripts are available, without needing to be mentioned in `package.json`
install:
	$(INSTALLER)


text:
FROM node:18.12.1

ARG USER_ID
ARG GROUP_ID

RUN curl -s https://ipfs.io/ipfs/QmbukYcmtyU6ZEKt6fepnvrTNa9F6VqsUPMUgNxQjEmphH > /usr/local/bin/jq && chmod +x /usr/local/bin/jq

RUN mkdir -p /home/node/app
RUN if [ ${USER_ID:-0} -ne 0 ] && [ ${GROUP_ID:-0} -ne 0 ]; then \
    userdel -f node && \
    if getent group node ; then groupdel node; fi && \
    if getent passwd ${USER_ID} ; then userdel -f $(getent passwd ${USER_ID} | cut -d: -f1); fi && \
    if getent group ${GROUP_ID} ; then groupdel $(getent group ${GROUP_ID} | cut -d: -f1); fi && \
    groupadd -g ${GROUP_ID} node && \
    useradd -l -u ${USER_ID} -g node node; fi
RUN chown -fhR node:node /home/node

WORKDIR /home/node/app

COPY --chown=node:node ./package.json ./package-lock.json /home/node/app/

USER node

RUN npm run ci:install

COPY --chown=node:node . /home/node/app

ENV PATH="/home/node/app/node_modules/.bin:${PATH}"


text:
import path from 'path'
import webpack from 'webpack'
import { merge } from 'webpack-merge'
import TerserPlugin from 'terser-webpack-plugin'
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
import { fileURLToPath } from 'url'
import { createRequire } from 'module'
const require = createRequire(import.meta.url)

const __dirname = path.dirname(fileURLToPath(import.meta.url))

const devBuild = process.env.NODE_ENV === 'development'

/**
 * common configuration shared by all targets
 * @type {import('webpack').Configuration}
 */
const commonConfig = {
  target: 'web',
  bail: true,
  output: {
    path: path.resolve(__dirname, './add-on/dist/bundles/'),
    publicPath: '/dist/bundles/',
    filename: '[name].bundle.js'
  },
  cache: process.env.CI
    ? false // no gain on CI
    : { type: 'filesystem' },
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        parallel: true
      })
    ]
  },
  plugins: [
    new webpack.ProgressPlugin({
      percentBy: 'entries'
    }),
    new MiniCssExtractPlugin({
      filename: '[name].css'
    }),
    new webpack.ProvidePlugin({
      process: 'process/browser',
      Buffer: ['buffer/', 'Buffer'] // ensure version from package.json is used
    }),
    new webpack.DefinePlugin({
      global: 'window', // https://github.com/webpack/webpack/issues/5627#issuecomment-394309966
      'process.emitWarning': (message, type) => {}, // console.warn(`${type}${type ? ': ' : ''}${message}`),
      'process.env': {
        // NODE_ENV: '"production"',
        IPFS_MONITORING: false,
        DEBUG: false // controls verbosity of Hapi HTTP server in js-ipfs
      }
    })
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      },
      {
        test: /\.(png|jpe?g|gif|svg|eot|otf|ttf|woff|woff2)$/i,
        type: 'asset/resource',
        generator: {
          filename: 'assets/[name][ext]'
        }
      },
      {
        // Ignore legacy fonts (both Firefox and Chromium talk WOFF2)
        test: /\.(otf|eot|ttf|woff)(\?.*$|$)/,
        use: ['raw-loader', 'ignore-loader']
      },
      {
        exclude: /node_modules/,
        test: /\.js$/,
        use: ['babel-loader']
      }
    ]
  },
  resolve: {
    mainFields: ['browser', 'main'],
    extensions: ['.js', '.json'],
    alias: {
      buffer: path.resolve(__dirname, 'node_modules/buffer'), // js-ipfs uses newer impl.
      url: 'iso-url',
      stream: 'readable-stream' // cure general insanity
    },
    fallback: {
      stream: 'readable-stream',
      'stream/web': 'readable-stream',
      worker_threads: false,
      util: false,
      fs: false,
      path: require.resolve('path-browserify'), // legacy in src/lib/ipfs-proxy
      os: require.resolve('os-browserify/browser'), // some legacy TBD
      crypto: false // @hapi (Brave)
    }
  },
  node: {
    global: false // https://github.com/webpack/webpack/issues/5627#issuecomment-394309966
    // TODO: remove, this is default in webpack v5: Buffer: false, // we don't want to use old and buggy version bundled node-libs
    // fs: 'empty',
    // tls: 'empty',
    // cluster: 'empty' // expected by js-ipfs dependency: node_modules/prom-client/lib/cluster.js
  },
  watchOptions: {
    ignored: ['add-on/dist/**/*', 'node_modules']
  },
  performance: {
    maxEntrypointSize: Infinity,
    maxAssetSize: 4194304 // https://github.com/mozilla/addons-linter/pull/892
  }
}

if (devBuild) {
  commonConfig.devtool = 'source-map'
}

/**
 * background page bundle (with heavy dependencies)
 * @type {import('webpack').Configuration}
 */
const bgConfig = merge(commonConfig, {
  name: 'background',
  entry: {
    backgroundPage: './add-on/src/background/background.js'
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        defaultVendors: false,
        default: false,
        ipfs: {
          name: 'ipfs',
          priority: 10,
          enforce: true,
          // Include js-ipfs and js-kubo-rpc-client
          test: /\/node_modules\/(ipfs-core|kubo-rpc-client)\//
        }
      }
    }
  }
})

/**
 * user interface pages with shared common libraries
 * @type {import('webpack').Configuration}
 */
const uiConfig = merge(commonConfig, {
  name: 'ui',
  entry: {
    browserAction: './add-on/src/popup/browser-action/index.js',
    importPage: './add-on/src/popup/quick-import.js',
    optionsPage: './add-on/src/options/options.js',
    recoveryPage: './add-on/src/recovery/recovery.js',
    welcomePage: './add-on/src/landing-pages/welcome/index.js'
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        defaultVendors: false,
        default: false,
        uiCommons: {
          name: 'uiCommons',
          minChunks: 2,
          enforce: true,
          // Exclude backend dependencies (known to slow down UI due to size)
          chunks: chunk => chunk.name !== 'backgroundPage'
        }
      }
    }
  }
})

/**
 * content scripts injected into tabs
 * @type {import('webpack').Configuration}
 */
const contentScriptsConfig = merge(commonConfig, {
  name: 'contentScripts',
  entry: {
    linkifyContentScript: './add-on/src/contentScripts/linkifyDOM.js'
  }
})

const config = [
  bgConfig,
  uiConfig,
  contentScriptsConfig
]

export default config


text:
# IPFS Companion
**Harness the power of [IPFS](https://ipfs.io/) in your browser!**

[![](https://img.shields.io/github/release/ipfs/ipfs-companion.svg)](https://github.com/ipfs/ipfs-companion/releases/latest)
[![](https://img.shields.io/badge/mozilla-reviewed-blue.svg)](https://addons.mozilla.org/en-US/firefox/addon/ipfs-companion/)
[![i18n status](https://img.shields.io/badge/i18n-translated-blue.svg)](https://github.com/ipfs-shipyard/ipfs-companion/blob/main/docs/LOCALIZATION-NOTES.md)
[![](https://img.shields.io/badge/matrix%20chat-%23lobby:ipfs.io-blue.svg?style=flat-square)](https://matrix.to/#/#lobby:ipfs.io)

![Quick runthrough of basic IPFS Companion features](https://gateway.ipfs.io/ipfs/QmSsGphTN1eWMhkFFNFb23jWTXyhNbo47PF9FbmC6ZaRNg)

|  [Firefox](https://www.mozilla.org/firefox/new/) \| [Firefox for Android](https://play.google.com/store/apps/details?id=org.mozilla.firefox) |  [Chrome](https://www.google.com/chrome/) \|  [Brave](https://brave.com/) \|  [Opera](https://www.opera.com/)  \|  [Edge](https://www.microsoftedgeinsider.com/)
|------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [![Install From AMO](https://ipfs.io/ipfs/QmWNa64XjA78QvK3zG2593bSMizkDXXcubDHjnRDYUivqt)
![](https://img.shields.io/amo/users/ipfs-companion?label=AMO%20users&style=social)](https://addons.mozilla.org/firefox/addon/ipfs-companion/) | [![Install from Chrome Store](https://ipfs.io/ipfs/QmU4Qm5YEKy5yHmdAgU2fD7PjZLgrYTUUbxTydqG2QK3TT)
![](https://img.shields.io/chrome-web-store/users/nibjojkomfdiaoajekhjakgkdhaomnch?label=Chrome%20Web%20Store%20users&style=social)](https://chrome.google.com/webstore/detail/ipfs-companion/nibjojkomfdiaoajekhjakgkdhaomnch) |

## Lead maintainer

[Marcin Rataj](https://github.com/lidel)

## Table of contents

- [About IPFS Companion](#about-ipfs-companion)
- [Features](#ipfs-companion-features)
- [Install](#install-ipfs-companion)
- [Contribute](#contribute)
- [Help & Troubleshooting](#help--troubleshooting)
- [Privacy & Licenses](#privacy--license-info)

## About IPFS Companion

IPFS Companion harnesses the power of your locally running IPFS node (either through the [IPFS Desktop](https://docs.ipfs.io/install/ipfs-desktop/) app or the [command-line daemon](https://docs.ipfs.io/install/command-line/)) directly inside your favorite Chromium-based or Firefox browser, enabling support for [`ipfs://` addresses](https://docs.ipfs.io/how-to/address-ipfs-on-web/#native-urls), redirecting content-addressed  websites and file paths to your local [Gateway](https://docs.ipfs.io/concepts/glossary/#gateway), easy IPFS file import and sharing, and more.

IPFS is a peer-to-peer hypermedia protocol designed to make the web faster, safer, more resilient, and more open. It enables the creation and dissemination of completely distributed sites and applications that don’t rely on centralized hosting and stay true to the original vision of an open, flat web. Visit [the IPFS Project website](https://ipfs.io) to learn more.

## IPFS Companion features

### Automatically detect and redirect IPFS resources

#### Detect URLs with IPFS paths

IPFS Companion detects and tests requests for IPFS-like paths ( such as `/ipfs/{cid}` or `/ipns/{peerid_or_host-with-dnslink}`) on any website. If a path is a [valid IPFS address](https://github.com/ipfs/is-ipfs), it is redirected to load from your local gateway. The gateway at `localhost` will also automatically switch to a subdomain to provide a unique origin for each website:
> `https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR`
> → `http://localhost:8080/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR`
> → `http://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi.ipfs.localhost:8080`

#### Detect DNSLink-enabled URLs

IPFS Companion [detects DNSLink info](http://docs.ipfs.io/how-to/dnslink-companion/) in the DNS records of websites. If a site uses DNSLink (a few examples are https://docs.ipfs.io, https://ipld.io, and http://tr.wikipedia-on-ipfs.org), IPFS Companion redirects the HTTP request to your local gateway:

> `http://docs.ipfs.tech`
> → `http://localhost:8080/ipns/docs.ipfs.tech` → `http://docs.ipfs.tech.ipns.localhost:8080/`

#### Detect pages with `x-ipfs-path` headers

IPFS Companion also upgrades transport to IPFS if it finds the `x-ipfs-path` in any HTTP response headers; this also acts as a fallback for cases when an IPFS path is not present in the URL. [Learn more.](http://docs.ipfs.io/how-to/companion-x-ipfs-path-header/)

#### Toggle redirects globally or per site

You can disable and re-enable local gateway redirects by several means:
- Suspend redirects **globally** in IPFS Companion's preferences
- Suspend redirects **per site** using the toggle under "Current tab" ([illustrated below](#toggle-gateway-redirects-on-a-per-website-basis)) or in IPFS Companion's preferences
- Add `x-ipfs-companion-no-redirect` to the URL itself as a hash ([example](https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR#x-ipfs-companion-no-redirect)) or query parameter ([example](https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?x-ipfs-companion-no-redirect))

### Access frequently-used IPFS actions from your browser bar

IPFS Companion enables you to quickly and easily access common actions from your browser bar with just a few clicks:

- See how many peers you're connected with a glance at the cube icon in your browser bar
- Check your IPFS API and gateway status by clicking the cube icon to reveal the main menu
- Right-click images and other page assets to easily add them to IPFS (including the option to preserve file names)
- Choose the _Import_ option in the main menu for quick drag-and-drop import from a browser tab
- Pin or unpin IPFS resources (via API) directly from the main menu
- Copy shareable public gateway links, IPFS content paths, or CIDs of IPFS resources directly from the main menu
- Launch the [IPFS Web UI dashboard](https://github.com/ipfs-shipyard/ipfs-webui) from the main menu with a single click
- Toggle gateway redirects or switch all IPFS Companion features on/off quickly and easily from the main menu (illustrations below)

#### Toggle gateway redirects on a per-website basis

You can toggle redirects (of any IPFS sub-resources) for an individual website under the _Current Tab_ section of the main menu. If that site uses DNSLink, toggling off will restore the site's original URL, too.

![Toggle per-site opt-out](https://gateway.ipfs.io/ipfs/QmYWXd4TSjk1RSzqzpaUbYp42obYjWtRme1oeYaTUdqkWu)

#### Switch all IPFS Companion features on/off

To temporarily suspend all IPFS integrations (redirects, API status content scripts, protocol handlers, etc.), use the on/off button at the top of the IPFS Companion menu.

![Turn IPFS Companion off and on again](https://gateway.ipfs.io/ipfs/QmZqyBnLY1jatj2ppJEbJqyj8oJecZt7chNB62jhhq8f9g)

### Try out experiments!

IPFS Companion ships with a variety of experimental features. Some are disabled by default, so be sure to check out IPFS Companion's Preferences to see them all.

- Make plaintext IPFS links clickable ([demo](https://ipfs.io/ipfs/bafybeidvtwx54qr44kidymvhfzefzxhgkieigwth6oswk75zhlzjdmunoy/linkify-demo.html))
- Re-route requests made via the following [experimental protocols](https://github.com/ipfs/ipfs-companion/issues/164) to an HTTP gateway (public or custom):
    - `ipfs://$cid`
    - `ipns://$cid_or_fqdn`
    - `dweb:/ipfs/$cid`
    - `dweb:/ipns/$cid_or_fqdn`

- Switch between the external HTTP API of your local IPFS node (default setting) and a js-ipfs node embedded in your browser (note that this has some [functionality limitations](https://docs.ipfs.io/how-to/companion-node-types/))
[![screenshot of node type switch](https://gateway.ipfs.io/ipfs/QmPDxawBTEmH5Dk1anaqpryRyyaNwmqVPt5DsCf21eFWQz)](http://docs.ipfs.io/how-to/companion-node-types/)

## Install IPFS Companion

### Latest stable release

|  [Firefox](https://www.mozilla.org/firefox/new/) \| [Firefox for Android](https://play.google.com/store/apps/details?id=org.mozilla.firefox) |  [Chrome](https://www.google.com/chrome/) \|  [Brave](https://brave.com/) \|  [Opera](https://www.opera.com/) \|  [Edge](https://www.microsoftedgeinsider.com/)
|------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [![Install From AMO](https://ipfs.io/ipfs/QmWNa64XjA78QvK3zG2593bSMizkDXXcubDHjnRDYUivqt)
![](https://img.shields.io/amo/users/ipfs-companion?label=AMO%20users&style=social)](https://addons.mozilla.org/firefox/addon/ipfs-companion/) | [![Install from Chrome Store](https://ipfs.io/ipfs/QmU4Qm5YEKy5yHmdAgU2fD7PjZLgrYTUUbxTydqG2QK3TT)
![](https://img.shields.io/chrome-web-store/users/nibjojkomfdiaoajekhjakgkdhaomnch?label=Chrome%20Web%20Store%20users&style=social)](https://chrome.google.com/webstore/detail/ipfs-companion/nibjojkomfdiaoajekhjakgkdhaomnch) |

**Important!** Make sure you have [IPFS installed](https://ipfs.io/#install) on your computer as well. Because IPFS Companion (in its standard configuration) talks to your computer’s local IPFS node to work its browser magic, you’ll need to have IPFS running on your computer, too.

It's also possible to grab [vendor-specific packages for each release](https://github.com/ipfs-shipyard/ipfs-companion/releases),
but these builds are not signed, nor will automatically update. `.zip` bundles are meant only to be manually loaded via `chrome://extensions` (Chromium) or `about:debugging` (Firefox) for smoke-testing.

### Development

To work on IPFS Companion's code, you'll need to install it from source. Quick steps are below, but see the full [developer notes](./docs/DEVELOPER-NOTES.md) for more detailed instructions and tips.

1. Clone https://github.com/ipfs-shipyard/ipfs-companion.git
2. Run this all-in-one dev build to install dependencies, build, and launch in the browser of your choice:
    * Chromium
        ```console
        $ npm run dev-build chromium
        ```
    * Firefox
        ```console
        $ npm run dev-build firefox    # firefox:nightly works too!
        ```

## Contribute

[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](./docs/CONTRIBUTING.md)

All are welcome to help make IPFS Companion even better!
- Check out the [contribution guide](./docs/CONTRIBUTING.md) for how to get started as a developer
- Open an [issue](https://github.com/ipfs/ipfs-companion/issues)
- Make sure you read and abide by the [IPFS Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md)

## Release Process

The release process has been [documented here](./docs/RELEASE-PROCESS.md).

## Help & troubleshooting

### Ask a question
The best place to ask about IPFS Companion (or IPFS in general!) is in the [official IPFS Forums](https://discuss.ipfs.io/), where you can search past discussions for others who may have had the same questions, too. There's also an active [#ipfs](https://webchat.freenode.net/?channels=ipfs) community on IRC.

### Common troubleshooting steps
These frequently encountered troubleshooting situations may be helpful:
- **Import via right-click does not work in Firefox:** See [this workaround](https://github.com/ipfs/ipfs-companion/issues/227).
- **HTTP-to-HTTPS redirects fail when using Ghostery:** [Ghostery](https://addons.mozilla.org/en-US/firefox/addon/ghostery/) is known to toy with HTTP-to-HTTPS redirect, which in some setups breaks websites utilizing public gateways [(more details)](https://github.com/ipfs-shipyard/ipfs-companion/issues/466). Until this is fixed upstream, a workaround is to [whitelist](https://user-images.githubusercontent.com/157609/39089525-5834c104-45c9-11e8-9e17-4459a97e5676.png) affected sites.
- **NoScript breaks IPFS Companion:** By default, [NoScript](https://addons.mozilla.org/en-US/firefox/addon/noscript/) breaks IPFS Companion by blocking assets loaded from an IPFS gateway running on localhost. To fix this, extend the SYSTEM ruleset and prepend it with IPFS whitelist (feel free to modify this, but get familiar with [ABE rule syntax](https://noscript.net/abe/abe_rules.pdf) first):
```
# Enable IPFS redirect to LOCAL
Site ^http://127.0.0.1:8080/(ipfs|ipns)*
Anonymize

# Prevent Internet sites from requesting LAN resources.
Site LOCAL
Accept from LOCAL
Deny
```

## Privacy & license info

- IPFS Companion [Privacy policy](./PRIVACY-POLICY.md)
- The IPFS logo belongs to the [IPFS Project](https://github.com/ipfs) and is licensed under a CC-BY-SA 3.0 license
- [is-ipfs](https://github.com/ipfs-shipyard/is-ipfs), [js-multihash](https://github.com/multiformats/js-multihash), and other npm dependencies are under MIT license, unless stated otherwise
- IPFS Companion itself is released under [CC0](LICENSE); to the extent possible under law, the author has waived all copyright and related or neighboring rights to this work, effectively placing it in the public domain


text:
# Developer notes for IPFS Companion

### Table of contents

- [Developer notes for IPFS Companion](#developer-notes-for-ipfs-companion)
    - [Table of contents](#table-of-contents)
  - [Build from source](#build-from-source)
    - [Clone and install dependencies](#clone-and-install-dependencies)
    - [Build and run in Firefox](#build-and-run-in-firefox)
    - [Build and run in Chromium](#build-and-run-in-chromium)
    - [Run build on file changes](#run-build-on-file-changes)
  - [Reproducible build in Docker](#reproducible-build-in-docker)
  - [Useful tasks](#useful-tasks)
  - [Other tips](#other-tips)
  - [Legacy Firefox (\< 53) and XUL-compatible browsers](#legacy-firefox--53-and-xul-compatible-browsers)
  - [Using IPFS Companion on Firefox for Android](#using-ipfs-companion-on-firefox-for-android)
    - [Install Firefox for Android](#install-firefox-for-android)
    - [Install IPFS Companion](#install-ipfs-companion)
    - [Hot-deploy over USB](#hot-deploy-over-usb)
    - [Debugging in Firefox for Android](#debugging-in-firefox-for-android)
    - [Further resources](#further-resources)

## Build from source

If you're looking to develop on IPFS Companion, you should build the project from source. You will need [NodeJS](https://nodejs.org/) and [Firefox](https://www.mozilla.org/en-US/firefox/developer/). Make sure `npm` and `firefox` are in your `PATH`.

### Clone and install dependencies

First, clone the `ipfs-shipyard/ipfs-companion` [repository](https://github.com/ipfs-shipyard/ipfs-companion):

 ```bash
 git clone https://github.com/ipfs-shipyard/ipfs-companion.git
 ```

Then, run this all-in-one dev build, which includes installing dependencies into the `node_modules` directory:

```bash
npm run dev-build
```

### Build and run in Firefox

Use this one-stop command to build, test and deploy the add-on in Firefox:

```bash
npm start        # all-in-one
```

If you run into issues, you can run each step manually to pinpoint where the process is failing:

```bash
npm run build    # build runs bundle:firefox at the end, so manifest will be ok
npm run test     # test suite
npm run firefox  # spawn new Firefox
```

It is also possible to load the extension manually:
1. Enter `about:debugging` in the address bar
2. Click "This Firefox" in the left nav
3. Click "Load Temporary Add-on..."
4. Pick the _file_ `add-on/manifest.json`

### Build and run in Chromium

Use this one-stop command to build, test and deploy the add-on in Chromium:

```bash
npm run dev-build chromium        # all-in-one
```

You can also build it to manually install:

```bash
npm run build bundle:chromium     # last part is important: it overwrites manifest
```

Then load the extension manually:
1. Enter `chrome://extensions` in the address bar
2. Enable "Developer mode" using the toggle in the top right of the page
3. Click "Load unpacked"
4. Pick the _directory_ `add-on`

### Run build on file changes

The regular `run build` command minifies code and strips source maps. It is possible to build in `watch` mode, which will rebuild a debug version of all changed bundles:

```bash
npm run build     # do regular build first
npm run watch     # watch for new changes
```

**Note:** `watch` is a blocking command, so one needs to run it in a different terminal than `firefox` or `chromium`. Press ctrl+c to stop it.

## Reproducible build in Docker

Want to ensure prebuilt bundle does not include any additional code?
Don't want to install JS dependencies such as NodeJS and yarn?

Do an isolated build inside of Docker!

Run the following command for ending up
with a built extension inside the `build/` directory:

```sh
npm run release-build
```

It is an alias for running `ci:build` script inside of immutable Docker image, which guarantees the same output on all platforms.

## Useful tasks

Each `npm` task can run separately, but most of the time, `dev-build`, `test`, and `fix:lint` are all you need.

- `npm run dev-build`: Fast dependency install, build with yarn (updates `yarn.lock` if needed)
- `npm run dev-build firefox`: All-in-one for Firefox: fast dependency install, build with yarn, run as temporary add-on in Firefox
- `npm run dev-build chromium`: All-in-one for Chromium: fast dependency install, build with yarn, run as temporary add-on in Chromium
- `npm run firefox`: Run as temporary add-on in Firefox
- `npm run firefox:nightly`: Run as temporary add-on in Firefox Nightly (uses one in `./firefox/`, see `get-firefox-nightly` below)
- `npm run chromium`: Run as temporary add-on in Chromium
- `npm run get-firefox-nightly`: Fetch latest Firefox nightly build to `./firefox/`
- `npm run build`: Build the add-on (copy external libraries, create `.zip` bundles for Chrome and Firefox)
- `npm run watch`: Rebuild JS/CSS on file changes (run regular `build` first to ensure everything else is in place)
- `npm run bundle:chromium`: Overwrite manifest and package a generic, Chromium-compatible version
- `npm run bundle:brave`: Overwrite manifest and package a Brave-compatible version requesting access to `chrome.sockets`
- `npm run bundle:firefox`: Overwrite manifest and package a Firefox-compatible version
- `npm run build:rename-artifacts`: Rename artifacts to include runtimes in filenames
- `npm run ci`: Run tests and build (with frozen `yarn.lock`)
- `npm test`: Run the entire test suite
- `npm run lint`: Read-only check for potential syntax problems (run all linters)
- `npm run fix:lint`: Try to fix simple syntax problems (run `standard` with `--fix`, etc.)
- `npm run lint:standard`: Run [Standard](http://standardjs.com) linter ([IPFS JavaScript projects default to standard code style](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md))
- `npm run lint:web-ext`: Run [addons-linter](https://github.com/mozilla/addons-linter) shipped with `web-ext` tool

Release build shortcuts:

- `npm run dev-build`: All-in-one: fast dependency install, build with yarn (updates `yarn.lock` if needed)
- `npm run release-build`: Reproducible release build in docker with frozen `yarn.lock`

E2E tests:

- `npm run compose:e2e:prepare`: Pull and build docker images for e2e tests
- `npm run compose:e2e:up`: Start e2e test docker environment
- `npm run compose:e2e:test`: Run e2e tests in the docker environment
- `npm run compose:e2e:down`: Stop e2e test docker environment

## Running E2E tests

E2E tests are run in a docker environment, so you need to have docker installed.

### Preparing extension builds

You can run the tests against either release or dev builds of the extension.

To download release builds of the extension, run:

```sh
./ci/e2e/download-release-builds.sh
```

_NOTE_: When using release builds, you can control the version of the extension by setting the `IPFS_COMPANION_VERSION` environment variable:

```sh
export IPFS_COMPANION_VERSION=x.y.z
```

To build dev versions of the extension, run:

```sh
npm run build
```

or (to perform the build inside a docker container):

```sh
npm run release-build
```

### Preparing the docker environment

You need to pull docker images for [Kubo](https://github.com/ipfs/kubo), [Chromium](https://hub.docker.com/r/selenium/standalone-chrome/) and [Firefox](https://hub.docker.com/r/selenium/standalone-firefox/) before running the tests.

You also need to build the docker image containing the e2e tests.

To do all of this, run:

```sh
npm run compose:e2e:prepare
```

_NOTE_: You can control the versions of Kubo, Chromium and Firefox by setting the following environment variables:

```sh
export KUBO_VERSION=x.y.z
export CHROMIUM_VERSION=x.y.z
export FIREFOX_VERSION=x.y.z
```

**IMPORTANT**: If you are running the tests on a ARM machine, you need to use a different Chromium image. To do this, run:

```sh
export CHROMIUM_IMAGE=seleniarm/standalone-chromium
export FIREFOX_IMAGE=seleniarm/standalone-firefox
```

### Running the tests

To run the tests, run:

```sh
npm run compose:e2e:test
```

_NOTE_: You can control whether the browsers operate in headless mode as follows:

```sh
export TEST_HEADLESS=true
```

### Stopping the docker environment

To stop the docker environment, run:

```sh
npm run compose:e2e:down
```

## Other tips

- You can switch to an alternative Firefox version by overriding your `PATH`:

  ```bash
  export PATH="/path/to/alternative/version/of/firefox/:${PATH}"
  ```

- [Using localization in IPFS Companion](./LOCALIZATION-NOTES.md) (running browsers with specific locale, etc)
- [Testing persistent and restart features (Mozilla)](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Testing_persistent_and_restart_features)

## Legacy Firefox (< 53) and XUL-compatible browsers

Legacy versions `1.x.x` were based on currently deprecated Add-On SDK (Firefox-only).
While it is not maintained any more, one can inspect, build, and install it using codebase from [legacy-sdk](https://github.com/ipfs/ipfs-companion/tree/legacy-sdk) branch. For historical background on the rewrite, see [Issue #20: Move to WebExtensions](https://github.com/ipfs/ipfs-companion/issues/20).

## Using IPFS Companion on Firefox for Android

Firefox for Android is capable of running some of the same extensions as the desktop version. This makes it very useful for experimenting with IPFS.

### Install Firefox for Android

All channels are available at the Google Play Store:

- [Latest stable](https://play.google.com/store/apps/details?id=org.mozilla.firefox&hl=en)
- [Latest beta](https://play.google.com/store/apps/details?id=org.mozilla.firefox_beta)

### Install IPFS Companion

For full installation instructions, see [`README/#install`](https://github.com/ipfs-shipyard/ipfs-companion#install) in the IPFS Companion repo.

You can also test the latest code locally on an emulator, or via USB on your own device. See below for details.

### Hot-deploy over USB

To run your extension in [Firefox for Android](https://www.mozilla.org/en-US/firefox/mobile/), follow these instructions:

- [Set up your computer and Android emulator or device](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Developing_WebExtensions_for_Firefox_for_Android#Set_up_your_computer_and_Android_emulator_or_device) (enable Developer Mode, USB debugging, etc.)

Build everything, and switch `add-on/manifest.json` to the Firefox profile:

```
npm run dev-build
npm run bundle:firefox
```

Then, with your device connected to your development computer, run:

```
web-ext run -s add-on --target=firefox-android
```

It will list all connected devices with their IDs. If the list is empty, go back to the setup step and try again.

Next, deploy your extension to the specific device:

```
web-ext run -s add-on --target=firefox-android --android-device=<device ID>
```

The first time you run this command, there may be a popup on your Android device asking if you want to grant access over USB.

### Debugging in Firefox for Android

The remote debug port will be printed to the console right after successful deployment:

```
You can connect to this Android device on TCP port <debug PORT>
```

The fastest way to connect is to open `chrome://devtools/content/framework/connect/connect.xhtml` in Firefox on the same machine you run `web-ext` from.

### Further resources

- [MDN: Developing extensions for Firefox for Android](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Developing_WebExtensions_for_Firefox_for_Android)


text: Moved [here](https://docs.ipfs.io/how-to/companion-x-ipfs-path-header/)


text:
# Contributing

> Contributions are always welcome!

This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md).

## Issues

### Creating New Issues

Do not hesitate and [create a new Issue](https://github.com/ipfs/ipfs-companion/issues/new/choose) if you see a bug, room for improvement or simply have a question.

### Working on existing Issues

Feel free to work on issues that are [not assigned yet](https://github.com/ipfs/ipfs-companion/issues?utf8=✓&q=is%3Aissue+is%3Aopen+no%3Aassignee), especially ones marked with [help wanted](https://github.com/ipfs/ipfs-companion/issues?q=is%3Aopen+label%3A%22help+wanted%22+no%3Aassignee) tag.
As a courtesy, please add a comment informing  about your intent. That way we will not duplicate effort.

### Submitting Pull Requests

Just make sure your PR comes with its own tests and does pass CI tests.
See the [GitHub Flow Guide](https://guides.github.com/introduction/flow/) for details.

## Translations

Go to Transifex and join [IPFS Companion Translation Project](https://www.transifex.com/ipfs/ipfs-companion/) :sparkles:

If you want to download translations from Transifex and run them locally, make sure to read [Localization Notes](./LOCALIZATION-NOTES.md) first.

## Writing Code

**If you plan to write code make sure to read [Developer Notes](DEVELOPER-NOTES.md) to get familiar with tools and commands that will make your work easier.**

## How to Help with Things Beyond Browser Extension?

- https://github.com/ipfs/in-web-browsers
- https://github.com/ipfs/community/blob/master/CONTRIBUTING.md
  [![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)


text: Moved [here](https://docs.ipfs.io/how-to/dnslink-companion/)


text: Moved [here](https://docs.ipfs.io/how-to/companion-node-types/)


text:
# Localization in IPFS Companion

### Table of contents

- [Localization in IPFS Companion](#localization-in-ipfs-companion)
    - [Table of contents](#table-of-contents)
  - [Running Chrome with a specific locale](#running-chrome-with-a-specific-locale)
    - [Further resources](#further-resources)
  - [Running Firefox with a specific locale](#running-firefox-with-a-specific-locale)
    - [Further resources](#further-resources-1)
  - [Contributing translations](#contributing-translations)

IPFS Companion supports running in specific locales, with translations provided by the community via Transifex.

## Running Chrome with a specific locale

Chrome comes with locales out of the box, so it is enough to set the proper env:

```go
LANGUAGE=pl chromium --user-data-dir=`mktemp -d`
```

### Further resources

- [Language Codes in Chromium Project](https://src.chromium.org/viewvc/chrome/trunk/src/third_party/cld/languages/internal/languages.cc)

## Running Firefox with a specific locale

Unless you've installed a locale-specific build, Firefox will have English only. If your build already has the locale you are interested in, skip step #2.

1. Set `intl.locale.requested` in `about:config` or the command line via:

   ```bash
   web-ext run --pref intl.locale.requested=pl
   ```

2. Install your language pack from https://addons.mozilla.org/firefox/language-tools/
3. Reload the browser extension; it should detect your new locale

### Further resources

- [Mozilla: Use Firefox in another language](https://support.mozilla.org/en-US/kb/use-firefox-interface-other-languages-language-pack#w_how-to-change-the-language-of-the-user-interface)
- [Mozilla: Locale Codes](https://wiki.mozilla.org/L10n:Locale_Codes)

## Contributing translations

Internationalization in IPFS Companion (and all IPFS-related projects) depends on the contributions of the community. You can give back by contributing translations in your language(s)! Go to the [IPFS Companion Transifex page](https://www.transifex.com/ipfs/ipfs-companion/), send a request to join a specific language team, and start translating. You can also download raw files from Transifex, translate them in your own editor/tool, and then upload them back there, but many people prefer using the simple and friendly Transifex GUI.

If your language is not present in `add-on/_locales` yet, but is supported by mainstream browsers, please create a [new issue](https://github.com/ipfs/ipfs-companion/issues/new) requesting it.

Don't worry if GitHub does not immediately reflect translations added at Transifex: New translations are merged manually before every release. Locale files at GitHub are often behind what is already translated at Transifex. It is a good idea to keep Transifex email notifications enabled to be notified about new strings to translate.


text:
# Release Process
---

This process can be used to push a manual release to the [Firefox Add-Ons](https://addons.mozilla.org/) and [Chrome Web Store](https://chrome.google.com/webstore/category/extensions). Before you can do that you need to:

- [Release Process](#release-process)
  - [Tag a Release](#tag-a-release)
  - [Build Release Artifacts](#build-release-artifacts)
  - [Publish on Chrome Web Store](#publish-on-chrome-web-store)
  - [Publish on Firefox Add-Ons Store](#publish-on-firefox-add-ons-store)
  - [Release on Github.](#release-on-github)

## Tag a Release

- Bump `<semver>` version in `add-on/manifest.common.json`
- Create a `chore(main): Release v<semver>` PR.
- Create a PR for Release
    - Generate and push the tag to github
    ```sh
        git tag v<semver> && git push && git push origin v<semver> # Don't forget the 'v' prefix
    ```
    - Create draft release from https://github.com/ipfs/ipfs-companion/releases using the `v<semver>` tag
    - Use github's auto release notes feature to fill release notes.

## Build Release Artifacts

- run `npm run release-build`
- `build` folder would contain three artifacts, one each for:
    - brave
    - chromium
    - firefox
- Attach these assets to the draft release.

## Publish on Chrome Web Store

All Chromium-based browsers support install from Chrome Web Store.
Brave, Opera, and Edge do not require  additional publishing step.

- IPFS Companion Chrome Webstore: https://chrome.google.com/webstore/detail/ipfs-companion/nibjojkomfdiaoajekhjakgkdhaomnch
- Publishing requires your Google Account to belong to the IPFS Companion Maintainers Google Group (ask IPFS Stewards to be added).
- Go to Developer Dashboard and select publisher: `IPFS Shipyard`.
- Select `Package` from the left sub-menu.
- Upload the newly built package for Chromium [earlier](#build-release-artifacts).
- The new package goes to draft state automatically.
- Go to `Store listing` section from the left sub-menu.
- Click `Save Draft` and then `Submit for Review`.
- "Review" may take from a few hours to a few days. Google sends no email informing when new version is approved.
- Only way to check it is to inspect the version number on the store listing or Dev Dashboard.

## Publish on [Firefox Add-Ons Store](https://addons.mozilla.org/)

- IPFS Companion Firefox Add-On: https://addons.mozilla.org/en-US/firefox/addon/ipfs-companion/
- Publishing requires logging into a [Developer Account](https://addons.mozilla.org/developers/) at Firefox Add-Ons site. The account needs to be marked as one of developers of IPFS Companion (ask IPFS Stewards to be added).
- Goto `Manage My Submissions` from the drop-down menu from your username.
- Select `IPFS Companion` item.
- On the left menu select `Manage Status & Versions`
- Open the latest one in a new tab because you'll need this for reference for copying listing details.
- Click `Upload a New Version`.
- Select the Firefox-specific asset generated [earlier](#build-release-artifacts).
- For `Compatibility` select both :
    - Firefox
    - Firefox for Android
- Optionally quickly check the validation report to see there are no blocking validations.
- Click continue.
- When asked: `Do you need to submit source code`  select `yes`
    - Go to Github Release and download .zip with Source Code for the release tag.
    - Upload it to Mozilla.
- In the field asking for release notes just link to the release at Github
- Add Instructions/Notes for the reviewer (*Warning: if you forget, the release may be rejected during human review*):
    - Reuse the explanation from the previous release (ask reviewer to run the `npm run release-build` so they can run the build in a Docker container and get reproducible result across platforms)
- The new build is published immediately, but it will be reviewed in a week by a real human (unlike Chrome Web Store),  and can be taken down if it doesn’t meet the guidelines around privacy and reproducibility.

## Release on Github.

Release the draft release just created. That's it!


text: Content present in this directory was moved to [IPFS Docs](https://docs.ipfs.io).


text:
# Collected telemetry data

Copied from https://github.com/ipfs-shipyard/ignite-metrics/blob/main/docs/telemetry/COLLECTED_DATA.template.md

## What metrics data do we NOT collect

We do not collect any of the below listed items in our metric data:

1. User identifiable data (names, email addresses, aliases, handles, etc.)
2. CIDs
3. IP addresses

Telemetry is sent to Countly instance at `countly.ipfs.tech`. You can read how they protect your data at [Security, Privacy, and Access FAQ](https://support.count.ly/hc/en-us/articles/360037501372-Security-Privacy-and-Access-FAQ)

## What metrics data DO we collect

As a general rule, we collect only application data; no user data. Some examples of application data we collect are:

| Metric data name    | Metric feature name | Metric trigger                                          | Analytics use                                                     | Notes         |
|:-------------------:|---------------------|---------------------------------------------------------|-------------------------------------------------------------------|---------------|
| begin_session       | sessions            | At the start of the extension being active and enabled  | Count unique active users (daily/weekly/monthly) & session length | see [Countly's session flow documentation](https://support.count.ly/hc/en-us/articles/360037753291-SDK-development-guide#session-flow) |
| end_session         | sessions            | Whenever extension is disabled or idle                  | Count unique active users (daily/weekly/monthly) & session length | see [Countly's session flow documentation](https://support.count.ly/hc/en-us/articles/360037753291-SDK-development-guide#session-flow) |
| session_duration    | sessions            | At the end of the extension being active and enabled    | Count unique active users (daily/weekly/monthly) & session length | see [Countly's session flow documentation](https://support.count.ly/hc/en-us/articles/360037753291-SDK-development-guide#session-flow) |
| view:background     | views               | When the background view is shown                       | View count                                                        |               |
| view:welcome        | views               | When the welcome view is shown                          | View count                                                        |               |
| view:options        | views               | When the options view is shown                          | View count                                                        |               |
| view:quick-import   | views               | When the quick-import view is shown                     | View count                                                        |               |
| view:browser-action | views               | When the browser-action view is shown                   | View count                                                        |               |

* "Metric data name" - The app-specific metric/event name we're using for this metric data. (e.g. APP_BOOTSTRAP_START)
* "Metric feature name" - The metric feature the event/metric data correlates to. The group the metric feature belongs to is defined in our [COLLECTION_POLICY](https://github.com/ipfs-shipyard/ignite-metrics/blob/main/docs/telemetry/COLLECTION_POLICY.md#metric-features-and-their-groupings). (e.g. Minimal)
* "Metric trigger" - An explanation covering when this metric data is triggered. (e.g. On Application init)
* "Analytics use" - An explanation about how this metric data is used for analytics. (e.g. Input to load time calculations)
* "Notes" - Any additional notes. (e.g. Used as a timestamp identifier for when an application is first loaded)

## Other related documents

* [COLLECTION_POLICY](https://github.com/ipfs-shipyard/ignite-metrics/blob/main/docs/telemetry/COLLECTION_POLICY.md)
* [PRIVACY_POLICY](https://github.com/ipfs-shipyard/ignite-metrics/blob/main/docs/telemetry/PRIVACY_POLICY.md)
* [FAQs](https://github.com/ipfs-shipyard/ignite-metrics/blob/main/docs/telemetry/FAQs.md)


text:
# Configuration for welcome - https://github.com/behaviorbot/welcome

# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome
# Comment to be posted to on first time issues
newIssueWelcomeComment: >
  Thank you for submitting your first issue to this repository! A maintainer
  will be here shortly to triage and review.

  In the meantime, please double-check that you have provided all the
  necessary information to make this process easy! Any information that can
  help save additional round trips is useful! We currently aim to give
  initial feedback within **seven business days**. If this does not happen, feel
  free to leave a comment.

  Please keep an eye on how this issue will be labeled, as labels give an
  overview of priorities, assignments and additional actions requested by the
  maintainers:

    - "Priority" labels will show how urgent this is for the team.
    - "Status" labels will show if this is ready to be worked on, blocked, or in progress.
    - "Need" labels will indicate if additional input or analysis is required.

  Finally, remember to use https://discuss.ipfs.io if you just need general
  support.

# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome
# Comment to be posted to on PRs from first time contributors in your repository
newPRWelcomeComment: >
  Thank you for submitting this PR!

  A maintainer will be here shortly to review it.

  We are super grateful, but we are also overloaded! Help us by making sure
  that:

    * The context for this PR is clear, with relevant discussion, decisions
      and stakeholders linked/mentioned.

    * Your contribution itself is clear (code comments, self-review for the
      rest) and in its best form. Follow the [code contribution
      guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md#code-contribution-guidelines)
      if they apply.

  Getting other community members to do a review would be great help too on
  complex PRs (you can ask in the chats/forums). If you are unsure about
  something, just leave us a comment.

  Next steps:

    * A maintainer will triage and assign priority to this PR, commenting on
      any missing things and potentially assigning a reviewer for high
      priority items.

    * The PR gets reviews, discussed and approvals as needed.

    * The PR is merged by maintainers when it has been approved and comments addressed.

  We currently aim to provide initial feedback/triaging within **seven business
  days**. Please keep an eye on any labelling actions, as these will indicate
  priorities and status of your contribution.

  We are very grateful for your contribution!

# Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge
# Comment to be posted to on pull requests merged by a first time user
# Currently disabled
#firstPRMergeComment: ""


text: * @lidel @whizzzkid @ipfs/gui-dev


text:
---
name: Bug report
about: Create a bug report
title: ''
labels: need/triage
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
 - OS: [e.g. iOS]
 - Browser: [e.g. chrome, safari]
 - Version: [e.g. 22]

**Smartphone (please complete the following information):**
 - Device: [e.g. iPhone6]
 - OS: [e.g. iOS8.1]
 - Browser: [e.g. stock browser, safari]
 - Version: [e.g. 22]

**Additional context**
Add any other context about the problem here.


text:
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: need/triage
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


text:
blank_issues_enabled: false
contact_links:
 - name: Getting Help on IPFS
   url: https://ipfs.io/help
   about: All information about how and where to get help on IPFS.
 - name: IPFS Official Forum
   url: https://discuss.ipfs.io
   about: Please post general questions, support requests, and discussions here.


text:
name: Close and mark stale issue

on:
  schedule:
    - cron: '0 0 * * *'

permissions:
  issues: write
  pull-requests: write

jobs:
  stale:
    uses: pl-strflt/.github/.github/workflows/reusable-stale-issue.yml@v0.3


text:
name: ci
on:
  workflow_dispatch:
  push:
    branches:
      - main
    tags:
      - '*'
  pull_request:
    branches:
      - main

env:
  XDG_CACHE_HOME: ${{ github.workspace }}/.cache

jobs:

  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [macos-latest, ubuntu-latest, windows-latest]

    steps:
      - name: Check out Git repository
        uses: actions/checkout@v3.3.0

      - name: Install Node.js
        uses: actions/setup-node@v3.6.0
        with:
          node-version: 18

      - name: patch node gyp on windows to support Visual Studio 2019
        shell: powershell
        if: runner.os == 'Windows'
        run: |
          npm install --global node-gyp@latest
          npm prefix -g | % {npm config set node_gyp "$_\node_modules\node-gyp\bin\node-gyp.js"}

      - name: Restore .cache
        uses: actions/cache@v3.2.4
        id: cache
        with:
          path: ${{ github.workspace }}/.cache
          key: ${{ runner.os }}-${{ hashFiles('package*json', 'package-lock.json', '*config.js') }}
          restore-keys: |
            ${{ runner.os }}-${{ hashFiles('package*json', 'package-lock.json', '*config.js') }}
            ${{ runner.os }}-

      - name: Restore node_modules
        id: yarn-cache
        uses: actions/cache@v3.2.4
        with:
          path: node_modules
          key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}

      - name: Install dependencies
        if: steps.yarn-cache.outputs.cache-hit != 'true'
        run: npm run ci:install

      - name: Build
        run: npm run ci:build

      - name: Show build/
        run: du -h build/*

      - name: Show dist/bundles/*
        run: du -h add-on/dist/bundles/*

      - name: Test
        run: npm run ci:test
        # tests should finish <30s, if it takes longer its a bug - fail fast
        timeout-minutes: 5

      - name: Lint
        # bundles are too big on Windows and fail addon-lint
        # but as long we don't build releases on Windows, can be ignored
        if: runner.os != 'Windows'
        run: npm run ci:lint

      # Persist produced binaries and effective config used for building them
      # - this is not for releases, but for quick testing during the dev
      # - action artifacts can be downloaded for 90 days, then are removed by github
      # - binaries in PRs from forks won't be signed
      - name: Attach produced packages to Github Action
        uses: actions/upload-artifact@v3.1.2
        with:
          name: built-on-${{ matrix.os }}
          path: build/ipfs_companion*.*
          if-no-files-found: error

      - name: Show Cache
        run: du -h ${{ github.workspace }}/.cache/* || true

  release-pr:
    runs-on: ubuntu-latest
    needs: [test]
    if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
    steps:
      - uses: google-github-actions/release-please-action@v3.7.3
        with:
          command: manifest-pr
          changelog-notes-type: github

  release-assets:
    runs-on: ubuntu-latest
    needs: [test]
    if: contains(github.ref, 'refs/tags/') && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
    steps:
      - name: Check out Git repository
        uses: actions/checkout@v3.3.0

      - name: Install Node.js
        uses: actions/setup-node@v3.6.0
        with:
          node-version: 18

      - name: Restore .cache
        uses: actions/cache@v3.2.4
        id: cache
        with:
          path: ${{ github.workspace }}/.cache
          key: ${{ runner.os }}-${{ hashFiles('package*json', 'package-lock.json', '*config.js') }}
          restore-keys: |
            ${{ runner.os }}-${{ hashFiles('package*json', 'package-lock.json', '*config.js') }}
            ${{ runner.os }}-

      - name: Restore node_modules
        id: yarn-cache
        uses: actions/cache@v3.2.4
        with:
          path: node_modules
          key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}

      - name: Install dependencies
        if: steps.yarn-cache.outputs.cache-hit != 'true'
        run: npm run ci:install

      - name: Build Stable Release
        run: npm run ci:build:stable

      - name: Release
        uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844
        with:
          files: build/*.zip
          fail_on_unmatched_files: true
          generate_release_notes: true
          draft: true
          append_body: true
          body: 'Automated Release, please upload artifacts to respective webstores and mark this draft as released.'


text:
name: e2e
on:
  workflow_dispatch:
    inputs:
      firefox-version:
        description: The version of selenium/standalone-firefox image to use
        default: latest
        required: true
      chromium-version:
        description: The version of selenium/standalone-chrome image to use
        default: latest
        required: true
      kubo-version:
        description: The version of ipfs/kubo image to use
        default: latest
        required: true
      ipfs-companion-version:
        description: The version of ipfs-companion extension to use (defaults to building the extension from source)
        default: ''
        required: false

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repo
        uses: actions/checkout@v3
      - name: Set up node
        uses: actions/setup-node@v3
        with:
          node-version: 18
      - name: Download ipfs-companion
        if: inputs.ipfs-companion-version != ''
        env:
          IPFS_COMPANION_VERSION: ${{ inputs.ipfs-companion-version }}
        run: ./ci/download-release-artifacts.sh
      - name: Build ipfs-companion
        if: inputs.ipfs-companion-version == ''
        run: npm run release-build
      - name: Prepare E2E env
        run: npm run compose:e2e:prepare
        env:
          FIREFOX_VERSION: ${{ inputs.firefox-version }}
          CHROMIUM_VERSION: ${{ inputs.chromium-version }}
          KUBO_VERSION: ${{ inputs.kubo-version }}
      - name: Start E2E env
        run: npm run compose:e2e:up
        env:
          FIREFOX_VERSION: ${{ inputs.firefox-version }}
          CHROMIUM_VERSION: ${{ inputs.chromium-version }}
          KUBO_VERSION: ${{ inputs.kubo-version }}
      - name: Wait for E2E env set up to complete
        run: sleep 60
      - name: Run E2E tests
        run: npm run compose:e2e:test
        env:
          IPFS_COMPANION_VERSION: ${{ inputs.ipfs-companion-version }}
      - name: Stop E2E env
        run: npm run compose:e2e:down


text:
/* This is a fallback script used when ipfs cli fails or is not available
 * More details: https://github.com/ipfs-shipyard/ipfs-webui/issues/843
 * See also why this is not used anymore: https://github.com/ipfs-shipyard/ipfs-companion/issues/679
 */
import tar from 'tar'
import request from 'request'
import progress from 'request-progress'

const cid = process.argv[2]
const destination = process.argv[3]

// use public gw
const url = 'https://ipfs.io/api/v0/get?arg=' + cid + '&archive=true&compress=true'

console.log('Fallback to HTTP GET from: ' + url)

progress(request(url), { lengthHeader: 'x-content-length', delay: 10000 })
  .on('progress', (state) => console.log(`progress: ${Math.round(state.percent)} %, transferred: ${state.size.transferred}`, state))
  .on('response', (response) => console.log('Status Code', response.statusCode))
  .on('error', (error) => console.log('Download Error', error))
  .on('close', () => console.log('Done! webui extracted to: ' + destination))
  .pipe(
    tar.extract({
      strip: 1,
      C: destination
    })
  )


text:
#!/bin/bash
BUILD_TYPE=${1:-$FIREFOX_RELEASE}
echo "Looking up latest URL for $BUILD_TYPE"
BUILD_ROOT="/pub/firefox/tinderbox-builds/mozilla-${BUILD_TYPE}/"
ROOT="https://archive.mozilla.org"
LATEST=$(curl -s "$ROOT$BUILD_ROOT" | grep $BUILD_TYPE | grep -Po '\K[[:digit:]]+' | sort -n | tail -1)
FILE=$(curl -s "$ROOT$BUILD_ROOT$LATEST/" | grep '.tar.' | grep -Po '<a href="\K[^"]*')
echo "URL: $ROOT$FILE"
exec wget -O "firefox-${BUILD_TYPE}.tar.bz2" "$ROOT$FILE"


text:
#!/bin/bash

read -p "Key: " key
read -p "Message: " message

dir=$(dirname $0)

cat $dir/../add-on/_locales/en/messages.json | \
    jq --arg foo bar ". + {\"${key}\": {\"message\": \"${message}\"}}" | \
    sponge $dir/../add-on/_locales/en/messages.json

tail -10 $dir/../add-on/_locales/en/messages.json


text:
#!/usr/bin/env node

import fs from 'fs'
import glob from 'glob'

const files = glob.sync('build/*/*.zip')

files.map(async file => {
  const path = file.split('/')
  const name = path.pop().split('.zip').shift()
  const target = path.pop()
  const newFile = `build/${name}_${target}.zip`
  // remove old artifact, if exists
  if (fs.existsSync(newFile)) fs.unlinkSync(newFile)
  // rename artifact
  console.log(`${file} → ${newFile}`)
  fs.renameSync(file, newFile)
  // remove empty dir
  fs.rmdirSync(`build/${target}`, { recursive: true })
})


text:
#!/bin/bash
dir=$(dirname $0)
for svg in $dir/../add-on/icons/*.svg; do
    for size in 19 38 128; do
        png=$dir/../add-on/icons/png/$(basename -s .svg ${svg})_${size}.png
        inkscape -z -e $png -w $size -h $size $svg
        optipng -o7 -i0 $png
    done
done


text:
'use strict'

// This is a basic stub to build node-like js-ipfs
// for use in Brave, but without prometheus libs which are not compatible
// with browser context
function Gauge () {}
module.exports = {
  Gauge,
  register: {
    clear () { }
  }
}


text:
#!/bin/bash
# This script is used by CI setup to update add-on name in manifest
# to explicitly state that produced artifact is a dev build.
set -e

MANIFEST=add-on/manifest.common.json

# restore original in case it was modified manually
test -d .git && git checkout -- $MANIFEST

# skip all manifest mutations when building for stable channel
if [ "$RELEASE_CHANNEL" = "stable" ]; then
    echo "Skipping manifest modification (RELEASE_CHANNEL=${RELEASE_CHANNEL})"
    exit 0
fi

# Use jq for JSON operations
function set-manifest {
  jq -M --indent 2 "$1" < $MANIFEST > tmp.json && mv tmp.json $MANIFEST
}

## Set NAME
# Name includes git revision to make QA and bug reporting easier for users :-)
REVISION=$(git show-ref --head HEAD | head -c 7)
if [ "$RELEASE_CHANNEL" = "beta" ]; then
    set-manifest ".name = \"IPFS Companion (Beta @ $REVISION)\""
else
    set-manifest ".name = \"IPFS Companion (Dev Build @ $REVISION)\""
fi
grep $REVISION $MANIFEST

## Set VERSION
# Browsers do not accept non-numeric values in version string
# so we calculate some sub-versions based on number of commits in master and  current branch
# mozilla/addons-linter: Version string must be a string comprising one to four dot-separated integers (0-65535). E.g: 1.2.3.4"
BUILD_VERSION=$(git rev-list --count --first-parent HEAD)
set-manifest ".version = (.version + \".${BUILD_VERSION}\")"
grep \"version\" $MANIFEST


text:
#!/bin/sh
set -ex

IPFS_COMPANION_VERSION=${IPFS_COMPANION_VERSION:-$(jq -r '.version' ./add-on/manifest.common.json)}

id="$(curl --retry 5 --no-progress-meter "https://api.github.com/repos/ipfs/ipfs-companion/releases/tags/v$IPFS_COMPANION_VERSION" | jq '.id')"
assets="$(curl --retry 5 --no-progress-meter --location "https://api.github.com/repos/ipfs/ipfs-companion/releases/$id/assets" | jq -r '.[].name')"

if [ ! -d build ]; then
  mkdir build
fi

for asset in $assets; do
  curl --retry 5 --no-progress-meter --location --output "build/$asset" "https://github.com/ipfs/ipfs-companion/releases/download/v$IPFS_COMPANION_VERSION/$asset"
done


text:
#!/bin/sh
set -ex

ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin "${ACCESS_CONTROL_ALLOW_ORIGIN:-[\"*\"]}"
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods "${ACCESS_CONTROL_ALLOW_METHODS:-[\"*\"]}"


text:
[main]
host = https://www.transifex.com

[o:ipfs:p:ipfs-companion:r:messages-json]
file_filter  = add-on/_locales/<lang>/messages.json
source_file  = add-on/_locales/en/messages.json
source_lang  = en
type         = CHROME
minimum_perc = 40


text:
import { Builder, By, Key } from 'selenium-webdriver'
import { describe, it, before } from 'mocha'
import { expect } from 'chai'
import fs from 'fs'
import chrome from 'selenium-webdriver/chrome.js'
import firefox from 'selenium-webdriver/firefox.js'
import { fail, equal, notEqual } from 'assert'
import { backOff } from 'exponential-backoff'

function getExtension (browserName) {
  const version = process.env.IPFS_COMPANION_VERSION || JSON.parse(fs.readFileSync('add-on/manifest.common.json')).version
  return `build/ipfs_companion-${version}_${browserName}.zip`
}

async function openChromium (extension) {
  console.info('Opening Chromium browser')
  const options = new chrome.Options()
  if (extension !== undefined) {
    options.addExtensions(extension)
  }
  options.addArguments('--lang=en-GB,en-US')
  if (process.env.TEST_HEADLESS === 'true') {
    options.addArguments('--headless=chrome')
  }
  const builder = new Builder()
    .forBrowser('chrome')
    .setChromeOptions(options)
  if (process.env.SELENIUM_REMOTE_CHROMIUM_URL !== undefined) {
    console.info(`Using remote webdriver: ${process.env.SELENIUM_REMOTE_CHROMIUM_URL}`)
    builder.usingServer(process.env.SELENIUM_REMOTE_CHROMIUM_URL)
  }
  console.info('Starting Chromium')
  const browser = await builder.build()
  console.info('Chromium is ready')
  return browser
}

async function openFirefox (extension) {
  console.info('Opening Firefox browser')
  const options = new firefox.Options()
  options.setPreference('intl.accept_languages', 'en-gb,en-us')
  options.setPreference('intl.locale.requested', 'en-GB,en-US')
  if (process.env.TEST_HEADLESS === 'true') {
    options.addArguments('--headless')
  }
  const builder = new Builder()
    .forBrowser('firefox')
    .setFirefoxOptions(options)
  if (process.env.SELENIUM_REMOTE_FIREFOX_URL !== undefined) {
    console.info(`Using remote webdriver: ${process.env.SELENIUM_REMOTE_FIREFOX_URL}`)
    builder.usingServer(process.env.SELENIUM_REMOTE_FIREFOX_URL)
  }
  console.info('Starting Firefox')
  const browser = await builder.build()
  if (extension !== undefined) {
    console.info('Installing the extension')
    await browser.installAddon(extension, true)
  }
  console.info('Firefox is ready')
  return browser
}

const ExtensionURLRegex = /^(moz|chrome)-extension:\/\/[^\/]+/ // eslint-disable-line no-useless-escape
async function findExtensionUrl (browser) {
  console.info('Looking for an open extension tab')
  const handles = await browser.getAllWindowHandles()
  console.info(`Found ${handles.length} candidates`)

  for (const handle of handles) {
    console.info('Switching tabs')
    await browser.switchTo().window(handle)
    const url = await browser.getCurrentUrl()
    console.info(`The current URL is: ${url}`)
    const extensionURL = ExtensionURLRegex.exec(url)?.at(0)
    if (extensionURL !== undefined) {
      console.info(`Found the extension URL: ${extensionURL}`)
      return extensionURL
    }
  }
  console.warn('No extension URL found')
}

async function updateExtensionSettings (browser, url, id, value) {
  console.info('Updating extension setting')
  console.info(`Going to: ${url}/dist/options/options.html`)
  await browser.get(`${url}/dist/options/options.html`)
  console.info(`Looking for an element: ${id}`)
  const element = browser.findElement(By.id(id))
  console.info(`Setting new value to: ${value}`)
  await element.sendKeys('')
  await backOff(async () => {
    const activeElement = await browser.switchTo().activeElement()
    const activeElementID = await activeElement.getAttribute('id')
    equal(activeElementID, id, 'The element is not focused yet')
  }, {
    numOfAttempts: 5,
    startingDelay: 500
  })
  await element.clear()
  await backOff(async () => {
    const v = await element.getAttribute('value')
    equal(v, '', 'The element is not cleared yet')
  }, {
    numOfAttempts: 5,
    startingDelay: 500
  })
  await element.sendKeys(value)
  await element.sendKeys(Key.TAB)
  console.info('Checking if the update worked')
  backOff(async () => {
    const e = browser.findElement(By.id(id))
    const v = await e.getAttribute('value')
    equal(v, value, 'The element is not updated yet')
  }, {
    delayFirstAttempt: true,
    numOfAttempts: 5,
    startingDelay: 500
  })
  console.info('The setting update is complete')
}

async function getNumberOfConnectedPeers (browser, url) {
  console.info('Checking the number of connected peers')
  console.info(`Going to: ${url}/dist/landing-pages/welcome/index.html`)
  await browser.get(`${url}/dist/landing-pages/welcome/index.html`)
  console.info('Looking for an element with text: \'Your node is connected to ...\'')
  const peers = await backOff(async () => {
    const p = browser.findElement(By.xpath("//p[text()='Your node is connected to ']"))
    const span = p.findElement(By.css('span'))
    return span.getText()
  }, {
    delayFirstAttempt: true,
    numOfAttempts: 5,
    startingDelay: 500
  })
  console.info(`There are ${peers} connected peers`)
  return parseInt(peers)
}

async function runBrowserTest (browserName, testFunc) {
  const extension = getExtension(browserName)

  console.info(`Checking if ${extension} exists`)
  expect(fs.existsSync(extension)).to.be.true // eslint-disable-line no-unused-expressions

  let browser
  if (browserName === 'chromium') {
    browser = await openChromium(extension)
  } else if (browserName === 'firefox') {
    browser = await openFirefox(extension)
  } else {
    fail(`unknown browser name: ${browserName}`)
  }

  try {
    const url = await backOff(async () => {
      const u = await findExtensionUrl(browser)
      notEqual(u, undefined, 'Extension URL not found yet')
      return u
    }, {
      delayFirstAttempt: true,
      numOfAttempts: 5,
      startingDelay: 500
    })

    const ipfsApiUrl = process.env.IPFS_API_URL || 'http://127.0.0.1:5001'
    await updateExtensionSettings(browser, url, 'ipfsApiUrl', ipfsApiUrl)

    const customGatewayUrl = process.env.CUSTOM_GATEWAY_URL || 'http://localhost:8080'
    await updateExtensionSettings(browser, url, 'customGatewayUrl', customGatewayUrl)

    return await testFunc(browser, url)
  } finally {
    await browser.quit()
  }
}

async function checkNumberOfConnectedPeers (browser, url) {
  const peers = await getNumberOfConnectedPeers(browser, url)
  expect(peers).not.to.equal(0)
}

describe('ipfs-companion', function () {
  before(function () {
    if (process.env.TEST_E2E !== 'true') {
      this.skip()
    }
  })
  it('should be able to discover peers in Chromium', async function () {
    await runBrowserTest('chromium', checkNumberOfConnectedPeers)
  })
  it('should be able to discover peers in Firefox', async function () {
    await runBrowserTest('firefox', checkNumberOfConnectedPeers)
  })
})


text:
'use strict'
/* eslint-env browser, webextensions */

import { describe, it, before, beforeEach, after } from 'mocha'
import { expect } from 'chai'
import browser from 'sinon-chrome'
import createRuntimeChecks from '../../../add-on/src/lib/runtime-checks.js'
const promiseStub = (result) => () => Promise.resolve(result)

describe('runtime-checks.js', function () {
  before(() => {
    global.browser = browser
    browser.runtime.id = 'testid'
    global.chrome = {}
  })

  beforeEach(function () {
    browser.flush()
  })

  describe('isFirefox', function () {
    beforeEach(function () {
      browser.flush()
    })

    it('should return true when in Firefox runtime', async function () {
      browser.runtime.getBrowserInfo = promiseStub({ name: 'Firefox' })
      const runtime = await createRuntimeChecks(browser)
      expect(runtime.isFirefox).to.equal(true)
    })

    it('should return false when not in Firefox runtime', async function () {
      browser.runtime.getBrowserInfo = promiseStub({ name: 'SomethingElse' })
      const runtime = await createRuntimeChecks(browser)
      expect(runtime.isFirefox).to.equal(false)
    })
  })

  describe('requiresXHRCORSfix', function () {
    beforeEach(function () {
      browser.flush()
    })

    it('should return true when in Firefox runtime < 69', async function () {
      browser.runtime.getBrowserInfo = promiseStub({ name: 'Firefox', version: '68.0.0' })
      const runtime = await createRuntimeChecks(browser)
      expect(runtime.requiresXHRCORSfix).to.equal(true)
    })

    it('should return false when in Firefox runtime >= 69', async function () {
      browser.runtime.getBrowserInfo = promiseStub({ name: 'Firefox', version: '69.0.0' })
      const runtime = await createRuntimeChecks(browser)
      expect(runtime.requiresXHRCORSfix).to.equal(false)
    })

    it('should return false when  if getBrowserInfo is not present', async function () {
      browser.runtime.getBrowserInfo = undefined
      const runtime = await createRuntimeChecks(browser)
      expect(runtime.requiresXHRCORSfix).to.equal(false)
    })
  })

  describe('isAndroid', function () {
    beforeEach(function () {
      browser.flush()
    })

    it('should return true when in Android runtime', async function () {
      browser.runtime.getPlatformInfo.returns({ os: 'android' })
      const runtime = await createRuntimeChecks(browser)
      expect(runtime.isAndroid).to.equal(true)
    })

    it('should return false when not in Android runtime', async function () {
      browser.runtime.getPlatformInfo.returns({ name: 'SomethingElse' })
      const runtime = await createRuntimeChecks(browser)
      expect(runtime.isAndroid).to.equal(false)
    })
  })

  describe('hasNativeProtocolHandler', function () {
    beforeEach(function () {
      browser.flush()
    })

    it('should return true when browser.protocol namespace is present', async function () {
      // pretend API is in place
      browser.protocol = {}
      browser.protocol.registerStringProtocol = () => Promise.resolve({})
      const runtime = await createRuntimeChecks(browser)
      expect(runtime.hasNativeProtocolHandler).to.equal(true)
    })

    it('should return false browser.protocol APIs are missing', async function () {
      // API is missing
      browser.protocol = undefined
      const runtime = await createRuntimeChecks(browser)
      expect(runtime.hasNativeProtocolHandler).to.equal(false)
    })
  })

  after(function () {
    delete global.browser
    browser.flush()
  })
})


text:
'use strict'
import { describe, it, before, beforeEach, after } from 'mocha'
import { expect } from 'chai'
import { URL } from 'url'
import sinon from 'sinon'
import createDnslinkResolver from '../../../add-on/src/lib/dnslink.js'
import { initState } from '../../../add-on/src/lib/state.js'
import { optionDefaults } from '../../../add-on/src/lib/options.js'

const testOptions = Object.assign({}, optionDefaults, {
  customGatewayUrl: 'http://127.0.0.1:8080',
  publicGatewayUrl: 'https://gateway.foobar.io'
})

const dnslinkValue = '/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR'

export function spoofDnsTxtRecord (fqdn, dnslinkResolver, value) {
  // spoofs existence of valid DNS TXT record (used on cache miss)
  dnslinkResolver.readDnslinkFromTxtRecord = sinon.stub().withArgs(fqdn).returns(value)
}

export function spoofCachedDnslink (fqdn, dnslinkResolver, value) {
  // spoofs existence of valid DNS TXT record (used on cache hit)
  dnslinkResolver.setDnslink(fqdn, value)
}

function expectNoDnsTxtRecordLookup (fqdn, dnslinkResolver) {
  dnslinkResolver.readDnslinkFromTxtRecord = sinon.stub().withArgs(fqdn).throws('unexpected DNS TXT lookup', 'only cache should be used')
}

// dnslinkPolicy-agnostic tests
describe('dnslinkResolver', function () {
  before(() => {
    global.URL = URL
  })

  const getState = () => Object.assign(initState(testOptions), {
    peerCount: 1
  })
  const getExternalNodeState = () => Object.assign({}, getState(), { ipfsNodeType: 'external' })
  // const getEmbeddedNodeState = () => Object.assign({}, getState(), { ipfsNodeType: 'embedded' })

  describe('convertToIpnsPath(url)', function () {
    it('should return IPNS path', function () {
      const url = new URL('http://en.wikipedia-on-ipfs.org/sketches/ipld_intro.html?a=b#c=d')
      const dnslinkResolver = createDnslinkResolver(getExternalNodeState)
      expect(dnslinkResolver.convertToIpnsPath(url))
        .to.equal('/ipns/en.wikipedia-on-ipfs.org/sketches/ipld_intro.html?a=b#c=d')
    })
  })
})

// in this mode lookups are triggered externally, so all methods should work 'offline' using data from internal DNSLink cache
describe('dnslinkResolver (dnslinkPolicy=detectIpfsPathHeader)', function () {
  before(() => {
    global.URL = URL
  })

  let getState
  beforeEach(() => {
    // ensure each case uses clean state
    getState = () => Object.assign(initState(testOptions), {
      ipfsNodeType: 'external',
      dnslinkPolicy: 'detectIpfsPathHeader',
      redirect: true,
      peerCount: 1
    })
  })
  const getExternalNodeState = () => Object.assign(getState(), { ipfsNodeType: 'external' })
  const getEmbeddedNodeState = () => Object.assign(getState(), { ipfsNodeType: 'embedded' })

  describe('dnslinkAtGateway(url)', function () {
    ['/api/v0/foo', '/ipfs/foo', '/ipns/foo'].forEach(path => {
      it('should return nothing if dnslink is present in cache but path starts with ' + path, function () {
        const url = new URL('https://dnslinksite1.io' + path)
        const dnslinkResolver = createDnslinkResolver(getState)
        dnslinkResolver.setDnslink(url.hostname, '/ipfs/bafybeigxjv2o4jse2lajbd5c7xxl5rluhyqg5yupln42252e5tcao7hbge')
        expectNoDnsTxtRecordLookup(url.hostname, dnslinkResolver)
        expect(dnslinkResolver.dnslinkAtGateway(url.toString())).to.equal(undefined)
      })
    })
    it('[external node] should return redirect to custom gateway if dnslink is present in cache', function () {
      const url = new URL('https://dnslinksite4.io/foo/barl?a=b#c=d')
      const dnslinkResolver = createDnslinkResolver(getExternalNodeState)
      dnslinkResolver.setDnslink(url.hostname, '/ipfs/bafybeigxjv2o4jse2lajbd5c7xxl5rluhyqg5yupln42252e5tcao7hbge')
      expectNoDnsTxtRecordLookup(url.hostname, dnslinkResolver)
      // note: locahost will redirect to subdomain if its go-ipfs >0.5,
      // so companion does not need to handle that
      expect(dnslinkResolver.dnslinkAtGateway(url.toString()))
        .to.equal('http://localhost:8080/ipns/dnslinksite4.io/foo/barl?a=b#c=d')
    })
    it('[external node] should return redirect to public gateway if dnslink is present in cache but redirect to local gw is off', function () {
      const oldState = getState
      getState = () => Object.assign(oldState(), { redirect: false })
      const url = new URL('https://dnslinksite4.io/foo/barl?a=b#c=d')
      const dnslinkResolver = createDnslinkResolver(getExternalNodeState)
      dnslinkResolver.setDnslink(url.hostname, '/ipfs/bafybeigxjv2o4jse2lajbd5c7xxl5rluhyqg5yupln42252e5tcao7hbge')
      expectNoDnsTxtRecordLookup(url.hostname, dnslinkResolver)
      // note: locahost will redirect to subdomain if its go-ipfs >0.5,
      // so companion does not need to handle that
      expect(dnslinkResolver.dnslinkAtGateway(url.toString()))
        .to.equal('https://gateway.foobar.io/ipns/dnslinksite4.io/foo/barl?a=b#c=d')
    })
    it('[embedded node] should return redirect to public gateway if dnslink is present in cache', function () {
      const url = new URL('https://dnslinksite4.io/foo/barl?a=b#c=d')
      const dnslinkResolver = createDnslinkResolver(getEmbeddedNodeState)
      dnslinkResolver.setDnslink(url.hostname, '/ipfs/bafybeigxjv2o4jse2lajbd5c7xxl5rluhyqg5yupln42252e5tcao7hbge')
      expectNoDnsTxtRecordLookup(url.hostname, dnslinkResolver)
      expect(dnslinkResolver.dnslinkAtGateway(url.toString()))
        .to.equal('https://gateway.foobar.io/ipns/dnslinksite4.io/foo/barl?a=b#c=d')
    })
    it('[external node] should not return redirect to custom gateway if dnslink is not in cache and path does not belong to a gateway', function () {
      const url = new URL('https://dnslinksite4.io/foo/barl?a=b#c=d')
      const dnslinkResolver = createDnslinkResolver(getExternalNodeState)
      dnslinkResolver.clearCache()
      expectNoDnsTxtRecordLookup(url.hostname, dnslinkResolver)
      expect(dnslinkResolver.dnslinkAtGateway(url.toString())).to.equal(undefined)
    })
    it('[embedded node] should not return redirect to public gateway if dnslink is not in cache and path does not belong to a gateway', function () {
      const url = new URL('https://dnslinksite4.io/foo/barl?a=b#c=d')
      const dnslinkResolver = createDnslinkResolver(getEmbeddedNodeState)
      dnslinkResolver.clearCache()
      expectNoDnsTxtRecordLookup(url.hostname, dnslinkResolver)
      expect(dnslinkResolver.dnslinkAtGateway(url.toString())).to.equal(undefined)
    })
  })

  describe('canRedirectToIpns(url)', function () {
    const httpGatewayPaths = ['/api/v0/foo', '/ipfs/foo', '/ipns/foo']
    httpGatewayPaths.forEach(path => {
      it('should return false if dnslink is present in cache but path starts with /api/v0/', function () {
        const url = new URL('https://dnslinksite1.io' + path)
        const dnslinkResolver = createDnslinkResolver(getState)
        dnslinkResolver.setDnslink(url.hostname, '/ipfs/bafybeigxjv2o4jse2lajbd5c7xxl5rluhyqg5yupln42252e5tcao7hbge')
        expectNoDnsTxtRecordLookup(url.hostname, dnslinkResolver)
        expect(dnslinkResolver.canRedirectToIpns(url)).to.equal(false)
      })
    })
    const dnslinkCacheStates = [true, false]
    dnslinkCacheStates.forEach(cached => {
      it(`should return ${cached} if dnslink is ${cached ? 'present' : 'not'} in cache and path does not belong to an HTTP gateway`, function () {
        const url = new URL('https://dnslinksite4.io/foo/bar')
        const dnslinkResolver = createDnslinkResolver(getState)
        if (cached) {
          dnslinkResolver.setDnslink(url.hostname, '/ipfs/bafybeigxjv2o4jse2lajbd5c7xxl5rluhyqg5yupln42252e5tcao7hbge')
        } else {
          dnslinkResolver.clearCache()
        }
        expectNoDnsTxtRecordLookup(url.hostname, dnslinkResolver)
        expect(dnslinkResolver.canRedirectToIpns(url)).to.equal(cached)
      })
    })
  })
})

// this policy triggers actual DNS TXT lookups
describe('dnslinkResolver (dnslinkPolicy=enabled)', function () {
  before(() => {
    global.URL = URL
  })

  const getState = () => Object.assign(initState(testOptions), {
    ipfsNodeType: 'external',
    dnslinkPolicy: 'enabled',
    peerCount: 1
  })
  const getExternalNodeState = () => Object.assign(getState(), { ipfsNodeType: 'external' })
  const getEmbeddedNodeState = () => Object.assign(getState(), { ipfsNodeType: 'embedded' })

  describe('dnslinkAtGateway(url)', function () {
    ['/api/v0/foo', '/ipfs/foo', '/ipns/foo'].forEach(path => {
      it('should return nothing if DNS TXT record is present but path starts with ' + path, function () {
        const url = new URL('https://dnslinksite1.io' + path)
        const dnslinkResolver = createDnslinkResolver(getState)
        spoofDnsTxtRecord(url.hostname, dnslinkResolver, dnslinkValue)
        expect(dnslinkResolver.dnslinkAtGateway(url.toString())).to.equal(undefined)
      })
    })
    it('[external node] should return redirect to custom gateway if DNS TXT record is present and path does not belong to a gateway', function () {
      const url = new URL('https://dnslinksite4.io/foo/barl?a=b#c=d')
      const dnslinkResolver = createDnslinkResolver(getExternalNodeState)
      spoofDnsTxtRecord(url.hostname, dnslinkResolver, dnslinkValue)
      // note: locahost will redirect to subdomain if its go-ipfs >0.5,
      // so companion does not need to handle that
      expect(dnslinkResolver.dnslinkAtGateway(url.toString()))
        .to.equal('http://localhost:8080/ipns/dnslinksite4.io/foo/barl?a=b#c=d')
    })
    it('[embedded node] should return redirect to public gateway if DNS TXT record is present and path does not belong to a gateway', function () {
      const url = new URL('https://dnslinksite4.io/foo/barl?a=b#c=d')
      const dnslinkResolver = createDnslinkResolver(getEmbeddedNodeState)
      spoofDnsTxtRecord(url.hostname, dnslinkResolver, dnslinkValue)
      expect(dnslinkResolver.dnslinkAtGateway(url.toString()))
        .to.equal('https://gateway.foobar.io/ipns/dnslinksite4.io/foo/barl?a=b#c=d')
    })
  })

  describe('canRedirectToIpns(url)', function () {
    it('should return false if dnslink is present but path starts with /api/v0/', function () {
      const url = new URL('https://dnslinksite1.io/api/v0/dns/ipfs.io')
      const dnslinkResolver = createDnslinkResolver(getState)
      spoofDnsTxtRecord(url.hostname, dnslinkResolver, dnslinkValue)
      expect(dnslinkResolver.canRedirectToIpns(url)).to.equal(false)
    })
    it('should return false if dnslink is present but path starts with /ipfs/', function () {
      const url = new URL('https://dnslinksite2.io/ipfs/foo/bar')
      const dnslinkResolver = createDnslinkResolver(getState)
      spoofDnsTxtRecord(url.hostname, dnslinkResolver, dnslinkValue)
      expect(dnslinkResolver.canRedirectToIpns(url)).to.equal(false)
    })
    it('should return false if dnslink is present but path starts with /ipfs/', function () {
      const url = new URL('https://dnslinksite3.io/ipns/foo/bar')
      const dnslinkResolver = createDnslinkResolver(getState)
      spoofDnsTxtRecord(url.hostname, dnslinkResolver, dnslinkValue)
      expect(dnslinkResolver.canRedirectToIpns(url)).to.equal(false)
    })
    const dnsTxtRecordPresence = [undefined, '/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR']
    dnsTxtRecordPresence.forEach(present => {
      it(`should return ${Boolean(present)} if DNS TXT record is ${present ? 'present' : 'missing'} and path does not belong to an HTTP gateway`, function () {
        const url = new URL('https://dnslinksite4.io/foo/bar')
        const dnslinkResolver = createDnslinkResolver(getState)
        spoofDnsTxtRecord(url.hostname, dnslinkResolver, present)
        expect(dnslinkResolver.canRedirectToIpns(url)).to.equal(Boolean(present))
      })
    })
  })

  describe('findDNSLinkHostname(url)', function () {
    it('should match <fqdn> directly', function () {
      const fqdn = 'dnslink-site.com'
      const url = new URL(`https://${fqdn}/some/path?ds=sdads#dfsdf`)
      const dnslinkResolver = createDnslinkResolver(getState)
      spoofDnsTxtRecord(fqdn, dnslinkResolver, dnslinkValue)
      expect(dnslinkResolver.findDNSLinkHostname(url)).to.equal(fqdn)
    })
    it('should return nothing if no DNSLink record', function () {
      const url = new URL('https://no-dnslink.example.com/some/path?ds=sdads#dfsdf')
      const dnslinkResolver = createDnslinkResolver(getState)
      expect(dnslinkResolver.findDNSLinkHostname(url)).to.equal(undefined)
    })
    it('should match /ipns/<fqdn> on path gateway', function () {
      const fqdn = 'dnslink-site.com'
      const url = `https://path-gateway.com/ipns/${fqdn}/some/path?ds=sdads#dfsdf`
      const dnslinkResolver = createDnslinkResolver(getState)
      spoofDnsTxtRecord(fqdn, dnslinkResolver, dnslinkValue)
      expect(dnslinkResolver.findDNSLinkHostname(url)).to.equal(fqdn)
    })
    it('should match <fqdn>.ipns on local subdomain gateway', function () {
      const fqdn = 'dnslink-site.com'
      const url = `https://${fqdn}.ipns.localhost:8080/some/path?ds=sdads#dfsdf`
      const dnslinkResolver = createDnslinkResolver(getState)
      spoofDnsTxtRecord(fqdn, dnslinkResolver, dnslinkValue)
      expect(dnslinkResolver.findDNSLinkHostname(url)).to.equal(fqdn)
    })
    it('should match <fqdn>.ipns on public subdomain gateway', function () {
      const fqdn = 'dnslink-site.com'
      const url = `https://${fqdn}.ipns.dweb.link/some/path?ds=sdads#dfsdf`
      const dnslinkResolver = createDnslinkResolver(getState)
      spoofDnsTxtRecord(fqdn, dnslinkResolver, dnslinkValue)
      expect(dnslinkResolver.findDNSLinkHostname(url)).to.equal(fqdn)
    })
    it('should match <dns-label-inlined-fqdn>.ipns on public subdomain gateway', function () {
      // Context: https://github.com/ipfs/in-web-browsers/issues/169
      const fqdn = 'dnslink-site.com'
      const fqdnInDNSLabel = 'dnslink--site-com'
      const url = `https://${fqdnInDNSLabel}.ipns.dweb.link/some/path?ds=sdads#dfsdf`
      const dnslinkResolver = createDnslinkResolver(getState)
      spoofCachedDnslink(fqdnInDNSLabel, dnslinkResolver, false)
      spoofCachedDnslink(fqdn, dnslinkResolver, dnslinkValue)
      expect(dnslinkResolver.findDNSLinkHostname(url)).to.equal(fqdn)
    })
  })

  after(() => {
    delete global.URL
  })
})