Skip to content

Commit

Permalink
Merge branch 'main' into feat/recover-client
Browse files Browse the repository at this point in the history
* main:
  feat: add telemetry to companion (#1117)
  chore: update PRIVACY-POLICY.md (#1137)
  • Loading branch information
whizzzkid committed Jan 31, 2023
2 parents c6e6dc5 + 42eed02 commit c7e6956
Show file tree
Hide file tree
Showing 23 changed files with 4,380 additions and 465 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@
/coverage
/.nyc_output
/add-on/manifest.json

.DS_Store
.vscode
109 changes: 46 additions & 63 deletions PRIVACY-POLICY.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,29 @@
# **IPFS Companion Privacy Policy**

<em>First Posted: 2019-02-15<br/>
Last Update: 2021-11-21</em> ([change history](https://github.com/ipfs-shipyard/ipfs-companion/commits/master/PRIVACY-POLICY.md))
Last Update: 2023-01-27</em> ([change history](https://github.com/ipfs-shipyard/ipfs-companion/commits/main/PRIVACY-POLICY.md))

The IPFS Companion browser extension is owned by Protocol Labs Inc. and created
by the IPFS Project. We know you care about how your personal data is used and
we take your privacy seriously. That is why, at the moment, we don’t track or
collect any of your Personal Data, nor do we sell it to anyone else or use it
for advertising. If that changes, we'll let you know by updating this policy. By
using IPFS Companion, you are accepting and agreeing to this Privacy Policy.
This Privacy Policy governs the use of the IPFS Companion browser extension
offered by Protocol Labs, Inc. (“**IPFS Companion**” or the “**Service**”).

**What Does This Privacy Policy Cover?**
The Service is offered subject to your acceptance without modification of all
the terms and conditions herein, and all other other operating rules, policies
and procedures that may be updated from time to time. By accessing or using any
part of the Service, you agree to be bound by the terms and conditions of the
Privacy Policy. If you do not agree to all of the terms and conditions of this
Privacy Policy, then you may not access the Service.

This Privacy Policy explains that we don’t gather, track, nor permanently store
any of your Personal Data. It also covers Additional Privacy Protocols related
to the way the IPFS Companion extension allows people to add and retrieve data
to and from the IPFS Network (which is a peer-to-peer network). This Privacy
Policy doesn’t apply to projects run by other organizations outside IPFS and
Protocol Labs, even if we promote the use of those projects or happen to
contribute to them.
**Personal Information**

**What Information Do We Collect?**
We do not collect personal information from the users of the Service.

None. We don’t collect your Personal Data, period.
**Metrics**

**Explicitly Shared Data Will Be Publicly Available over IPFS**
We collect non-user-specific metrics via the Service. For more information on
how to change your preferences with respect to the metrics, please contact us
via methods mentioned below.

We do not collect, rent, store or sell your Personal Data to anyone. However
because IPFS Companion is a web extension that provides access to the real-time,
peer-to-peer IPFS Network (which is a public platform for which anyone may join
and participate) the data that you import to the IPFS Network using IPFS
Companion is then publicly available and accessible to everyone participating in
IPFS Network.

**Additional Privacy Protocols**
**Additional Privacy Considerations**

If you add files to the IPFS Network using the IPFS Companion extension, they
will be stored on your local IPFS Network node. Those files are also then cached
Expand All @@ -42,40 +32,33 @@ user’s local IPFS Network node. Generally, cached files will eventually expire
but it’s possible for a user with whom you have shared access to such files (by
sharing the relevant Content Identifier or CID) to pin that data, which means
the cached files then will not expire and will remain stored on such user’s
local IPFS Network node.

All content shared with the IPFS Network is public by default. This means your
files and data that you’ve added will be accessible to everyone who knows the
CID or queries the data on the IPFS Network. If you want to share certain
materials or data privately, you must encrypt such data before adding it to the
IPFS Network.

If you are using embedded JS IPFS node it will connect to bootstrap servers
hosted by Protocol Labs and some of your Personal Information, such as public
key and IP address of your IPFS node will be stored on the IPFS network publicly
as well to facilitate p2p exchanges.

If you are using “Linkify IPFS Addresses” or “Catch Unhandled
IPFS Protocols” experiments, websites will be able to detect you are running
IPFS Companion. This behavior can be changed on Preferences screen by disabling
mentioned experiments.

If you are using DNSLink (its lookup is enabled by default), then IPFS node
will be executing DNS queries for all domain names visited during browsing,
and those queries will use DNS resolver configured in your operating system.
To disable this behavior set "DNSLink lookup" to "Off" in Preferences.

**Will We Ever Change This Privacy Policy?**

We’re constantly trying to improve IPFS Companion, so we may need to change this
Privacy Policy sometimes. When we do, we will update the date at the top of this
Privacy Policy and will also post an update at https://ipfs.tech/companion-privacy/
We encourage you to periodically review this Privacy Policy to stay informed, which
is ultimately your responsibility. If you use IPFS Companion after any changes
to the Privacy Policy have been posted, that means you agree to all of those
changes.

**What If I Have Questions About This Policy?**

If you have any questions or concerns regarding our privacy policies, please
send us a message at <legalrequests@protocol.ai>.
local IPFS Network node. All content shared with the IPFS Network is public by
default. This means your files and data that you’ve added will be accessible to
everyone who knows the CID or queries the data on the IPFS Network. If you want
to share certain materials or data privately, you must encrypt such data before
adding it to the IPFS Network.


If you are using “Linkify IPFS Addresses” or “Catch Unhandled IPFS Protocols”
experiments, websites will be able to detect you are running IPFS Companion.
This behavior can be changed on the Preferences screen by disabling mentioned experiments.


If you are using DNSLink (its lookup is enabled by default), then the IPFS node
will be executing DNS queries for all domain names visited during browsing, and
those queries will use a DNS resolver configured in your operating system. To
disable this behavior, set "DNSLink lookup" to "Off" in Preferences.


**Contact Us**

Questions about our Privacy Policy? Please contact us at
<legalrequests@protocol.ai>. For general information, please reach out via a [new issue on our GitHub repo](https://github.com/ipfs/ipfs-companion/issues/new/choose).

**Changes to our Privacy Policy**

If we decide to change our Privacy Policy, we will post those changes on this
page and also at https://ipfs.tech/companion-privacy.

This document is CC-BY-SA. It was last updated January 24th, 2023.

40 changes: 40 additions & 0 deletions add-on/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,46 @@
"message": "Related Projects",
"description": "Projects section title (page_landingWelcome_projects_title)"
},
"option_header_telemetry": {
"message": "Telemetry",
"description": "A section header on the Preferences screen (option_header_telemetry)"
},
"option_telemetry_disclaimer": {
"message": "We're collecting minimal telemetry data to improve and prioritize our work. Please consent to the collection of these metrics to assist in our efforts!",
"description": "Disclaimer about telemetry collection in the telemetry section on the Preferences screen (option_telemetry_disclaimer)"
},
"option_telemetryGroupMinimal_title": {
"message": "Feature Telemetry",
"description": "A title for the 'minimal' grouping of metrics we collect (option_telemetryGroupMinimal_title)"
},
"option_telemetryGroupMinimal_description": {
"message": "Collect basic feature and usage metrics to help maintainers to prioritize work on the most useful features.",
"description": "A description for the 'minimal' grouping of metrics we collect (option_telemetryGroupMinimal_description)"
},
"option_telemetryGroupMarketing_title": {
"message": "Marketing title",
"description": "A title for the 'marketing' grouping of metrics we collect (option_telemetryGroupMarketing_title)"
},
"option_telemetryGroupMarketing_description": {
"message": "Marketing description",
"description": "A description for the 'marketing' grouping of metrics we collect (option_telemetryGroupMarketing_description)"
},
"option_telemetryGroupPerformance_title": {
"message": "Performance title",
"description": "A title for the 'performance' grouping of metrics we collect (option_telemetryGroupPerformance_title)"
},
"option_telemetryGroupPerformance_description": {
"message": "Performance description",
"description": "A description for the 'performance' grouping of metrics we collect (option_telemetryGroupPerformance_description)"
},
"option_telemetryGroupTracking_title": {
"message": "Tracking title",
"description": "A title for the 'tracking' grouping of metrics we collect (option_telemetryGroupTracking_title)"
},
"option_telemetryGroupTracking_description": {
"message": "Tracking description",
"description": "A description for the 'tracking' grouping of metrics we collect (option_telemetryGroupTracking_description)"
},
"recovery_page_title" : {
"message": "Node Offline | IPFS Companion",
"description": "Title of the recovery page (recovery_page_title)"
Expand Down
1 change: 1 addition & 0 deletions add-on/src/background/background.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
<meta charset="utf-8">
<script src="/dist/bundles/ipfs.bundle.js"></script>
<script src="/dist/bundles/backgroundPage.bundle.js"></script>
<body></body>
2 changes: 2 additions & 0 deletions add-on/src/background/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ browser.runtime.setUninstallURL(getUninstallURL(browser))

// init add-on after all libs are loaded
document.addEventListener('DOMContentLoaded', async () => {
browser.runtime.sendMessage({ telemetry: { trackView: 'background' } })
// setting debug namespaces require page reload to get applied
const debugNs = (await browser.storage.local.get({ logNamespaces: optionDefaults.logNamespaces })).logNamespaces
if (debugNs !== localStorage.debug) {
localStorage.debug = debugNs
window.location.reload()
}
// init inlined to read updated localStorage.debug
// @ts-expect-error - TS does not know about window.ipfsCompanion
window.ipfsCompanion = await createIpfsCompanion()
})
1 change: 1 addition & 0 deletions add-on/src/landing-pages/welcome/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export default function createWelcomePageStore (i18n, runtime) {
state.webuiRootUrl = null
let port
emitter.on('DOMContentLoaded', async () => {
browser.runtime.sendMessage({ telemetry: { trackView: 'welcome' } })
emitter.emit('render')
port = runtime.connect({ name: 'browser-action-port' })
port.onMessage.addListener(async (message) => {
Expand Down
66 changes: 33 additions & 33 deletions add-on/src/lib/ipfs-client/brave.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,39 +16,39 @@ const waitFor = (f, t) => pWaitFor(f, { interval: tickMs, timeout: t || Infinity
// wrapper for chrome.ipfs.* that gets us closer to ergonomics of promise-based browser.*
export const brave = hasBraveChromeIpfs()
? Object.freeze({
// This is the main check - returns true only in Brave and only when
// feature flag is enabled brave://flags and can be used for high level UI
// decisions such as showing custom node type on Preferences
getIPFSEnabled: async () =>
Boolean(await promisifyBraveCheck(chrome.ipfs.getIPFSEnabled)),

// Obtains a string representation of the resolve method
// method is one of the following strings:
// "ask" uses a gateway but also prompts them to install a local node
// "gateway" uses a gateway but also prompts them to install a local node
// "local" uses a gateway but also prompts them to install a local node
// "disabled" disabled by the user
// "undefined" everything else (IPFS feature flag is not enabled, error etc)
getResolveMethodType: async () =>
String(await promisifyBraveCheck(chrome.ipfs.getResolveMethodType)),

// Obtains the config contents of the local IPFS node
// Returns undefined if missing for any reason
getConfig: async () =>
await promisifyBraveCheck(chrome.ipfs.getConfig),

// Returns true if binary is present
getExecutableAvailable: async () =>
Boolean(await promisifyBraveCheck(chrome.ipfs.getExecutableAvailable)),

// Attempts to start the daemon and returns true if finished
launch: async () =>
Boolean(await promisifyBraveCheck(chrome.ipfs.launch)),

// Attempts to stop the daemon and returns true if finished
shutdown: async () =>
Boolean(await promisifyBraveCheck(chrome.ipfs.shutdown))
})
// This is the main check - returns true only in Brave and only when
// feature flag is enabled brave://flags and can be used for high level UI
// decisions such as showing custom node type on Preferences
getIPFSEnabled: async () =>
Boolean(await promisifyBraveCheck(chrome.ipfs.getIPFSEnabled)),

// Obtains a string representation of the resolve method
// method is one of the following strings:
// "ask" uses a gateway but also prompts them to install a local node
// "gateway" uses a gateway but also prompts them to install a local node
// "local" uses a gateway but also prompts them to install a local node
// "disabled" disabled by the user
// "undefined" everything else (IPFS feature flag is not enabled, error etc)
getResolveMethodType: async () =>
String(await promisifyBraveCheck(chrome.ipfs.getResolveMethodType)),

// Obtains the config contents of the local IPFS node
// Returns undefined if missing for any reason
getConfig: async () =>
await promisifyBraveCheck(chrome.ipfs.getConfig),

// Returns true if binary is present
getExecutableAvailable: async () =>
Boolean(await promisifyBraveCheck(chrome.ipfs.getExecutableAvailable)),

// Attempts to start the daemon and returns true if finished
launch: async () =>
Boolean(await promisifyBraveCheck(chrome.ipfs.launch)),

// Attempts to stop the daemon and returns true if finished
shutdown: async () =>
Boolean(await promisifyBraveCheck(chrome.ipfs.shutdown))
})
: undefined

export async function init (browser, opts) {
Expand Down
26 changes: 22 additions & 4 deletions add-on/src/lib/ipfs-companion.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import createRuntimeChecks from './runtime-checks.js'
import { createContextMenus, findValueForContext, contextMenuCopyAddressAtPublicGw, contextMenuCopyRawCid, contextMenuCopyCanonicalAddress, contextMenuViewOnGateway, contextMenuCopyPermalink, contextMenuCopyCidAddress } from './context-menus.js'
import { registerSubdomainProxy } from './http-proxy.js'
import { runPendingOnInstallTasks } from './on-installed.js'
import { handleConsentFromState, startSession, endSession, trackView } from './telemetry.js'
const log = debug('ipfs-companion:main')
log.error = debug('ipfs-companion:main:error')

Expand All @@ -33,6 +34,7 @@ export default async function init () {
// INIT
// ===================================================================
let ipfs // ipfs-api instance
/** @type {import('../types.js').CompanionState} */
let state // avoid redundant API reads by utilizing local cache of various states
let dnslinkResolver
let ipfsPathValidator
Expand All @@ -55,8 +57,11 @@ export default async function init () {
runtime = await createRuntimeChecks(browser)
state = initState(options)
notify = createNotifier(getState)
// ensure consent is set properly on app init
handleConsentFromState(state)

if (state.active) {
startSession()
// It's ok for this to fail, node might be unavailable or mis-configured
try {
ipfs = await initIpfsClient(browser, state)
Expand Down Expand Up @@ -167,6 +172,15 @@ export default async function init () {
const result = validIpfsOrIpns(path) ? resolveToPublicUrl(path) : null
return Promise.resolve({ pubGwUrlForIpfsOrIpnsPath: result })
}
if (request.telemetry) {
return Promise.resolve(onTelemetryMessage(request.telemetry, sender))
}
}

function onTelemetryMessage (request, sender) {
if (request.trackView) {
return trackView(request.trackView)
}
}

// PORTS (connection-based messaging)
Expand Down Expand Up @@ -365,11 +379,11 @@ export default async function init () {
// https://github.com/ipfs-shipyard/ipfs-companion/issues/398
if (runtime.isFirefox && ipfsPathValidator.isIpfsPageActionsContext(url)) {
if (sameGateway(url, state.gwURL) || sameGateway(url, state.apiURL)) {
await browser.pageAction.setIcon({ tabId: tabId, path: '/icons/ipfs-logo-on.svg' })
await browser.pageAction.setTitle({ tabId: tabId, title: browser.i18n.getMessage('pageAction_titleIpfsAtCustomGateway') })
await browser.pageAction.setIcon({ tabId, path: '/icons/ipfs-logo-on.svg' })
await browser.pageAction.setTitle({ tabId, title: browser.i18n.getMessage('pageAction_titleIpfsAtCustomGateway') })
} else {
await browser.pageAction.setIcon({ tabId: tabId, path: '/icons/ipfs-logo-off.svg' })
await browser.pageAction.setTitle({ tabId: tabId, title: browser.i18n.getMessage('pageAction_titleIpfsAtPublicGateway') })
await browser.pageAction.setIcon({ tabId, path: '/icons/ipfs-logo-off.svg' })
await browser.pageAction.setTitle({ tabId, title: browser.i18n.getMessage('pageAction_titleIpfsAtPublicGateway') })
}
await browser.pageAction.show(tabId)
}
Expand Down Expand Up @@ -553,6 +567,8 @@ export default async function init () {
await registerSubdomainProxy(getState, runtime)
shouldRestartIpfsClient = true
shouldStopIpfsClient = !state.active
// Any time the extension switches active state, start or stop the current session.
state.active ? startSession() : endSession()
break
case 'ipfsNodeType':
if (change.oldValue !== braveNodeType && change.newValue === braveNodeType) {
Expand Down Expand Up @@ -619,6 +635,8 @@ export default async function init () {
break
}
}
// ensure consent is set properly on state changes
handleConsentFromState(state)

if ((state.active && shouldRestartIpfsClient) || shouldStopIpfsClient) {
try {
Expand Down
4 changes: 2 additions & 2 deletions add-on/src/lib/notifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ export default function createNotifier (getState) {
return await browser.notifications.create({
type: 'basic',
iconUrl: browser.runtime.getURL('icons/ipfs-logo-on.svg'),
title: title,
message: message
title,
message
})
} catch (err) {
log.error('failed to create a notification', err)
Expand Down
12 changes: 10 additions & 2 deletions add-on/src/lib/options.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
'use strict'

import { isIPv4, isIPv6 } from 'is-ip'
import isFQDN from 'is-fqdn'
import { isIPv4, isIPv6 } from 'is-ip'

/**
* @type {Readonly<import('../types.js').CompanionOptions>}
*/
export const optionDefaults = Object.freeze({
active: true, // global ON/OFF switch, overrides everything else
ipfsNodeType: 'external',
Expand Down Expand Up @@ -31,7 +34,12 @@ export const optionDefaults = Object.freeze({
importDir: '/ipfs-companion-imports/%Y-%M-%D_%h%m%s/',
useLatestWebUI: false,
dismissedUpdate: null,
openViaWebUI: true
openViaWebUI: true,
telemetryGroupMinimal: true,
telemetryGroupPerformance: false,
telemetryGroupUx: false,
telemetryGroupFeedback: false,
telemetryGroupLocation: false
})

function buildDefaultIpfsNodeConfig () {
Expand Down
Loading

0 comments on commit c7e6956

Please sign in to comment.