Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(mv3): Manifest V3 Migration Checklist #1170

Merged
merged 12 commits into from
Mar 24, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v18.14.0
4 changes: 1 addition & 3 deletions add-on/manifest.chromium.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"minimum_chrome_version": "72",
"permissions": [
"<all_urls>",
"idle",
"tabs",
"notifications",
Expand All @@ -10,8 +9,7 @@
"contextMenus",
"clipboardWrite",
"webNavigation",
"webRequest",
"webRequestBlocking"
"webRequest"
],
"incognito": "not_allowed"
}
34 changes: 22 additions & 12 deletions add-on/manifest.common.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"manifest_version": 2,
"manifest_version": 3,
"name": "__MSG_manifest_extensionName__",
"short_name": "__MSG_manifest_shortExtensionName__",
"version": "2.22.1",
Expand All @@ -12,9 +12,10 @@
"128": "icons/png/ipfs-logo-on_128.png"
},
"background": {
"page": "dist/background/background.html"
"service_worker": "dist/bundles/backgroundPage.bundle.js",
"type": "module"
},
"browser_action": {
"action": {
"default_icon": {
"19": "icons/png/ipfs-logo-off_19.png",
"38": "icons/png/ipfs-logo-off_38.png",
Expand All @@ -29,15 +30,24 @@
"page": "dist/options/options.html"
},
"web_accessible_resources": [
"icons/png/ipfs-logo-off_19.png",
"icons/png/ipfs-logo-off_38.png",
"icons/png/ipfs-logo-off_128.png",
"icons/ipfs-logo-on.svg",
"icons/ipfs-logo-off.svg",
"dist/recovery/recovery.css",
"dist/recovery/recovery.html",
"dist/recovery/recovery.js"
{
"resources": [
"icons/png/ipfs-logo-off_19.png",
"icons/png/ipfs-logo-off_38.png",
"icons/png/ipfs-logo-off_128.png",
"icons/ipfs-logo-on.svg",
"icons/ipfs-logo-off.svg",
"dist/recovery/recovery.css",
"dist/recovery/recovery.html",
"dist/recovery/recovery.js"
],
"matches": [
"<all_urls>"
]
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a look at https://developer.chrome.com/docs/extensions/mv3/manifest/web_accessible_resources/ and the first line

Web-accessible resources are files inside an extension that can be accessed by web pages or other extensions. Extensions typically use this feature to expose images or other assets that need to be loaded in web pages, but any asset included in an extension's bundle can be made web accessible.

It appears that this is needed so that webpages can test if companion is being used, and if so, they can load recovery.html/js/css. Is that correct? are there other usecases where this is needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are resources needed by the extension to load a webpage from the extension.

They changed the way, we don't need <all_urls> fixing it.

],
"content_security_policy": "script-src 'self'; object-src 'self'; frame-src 'self';",
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'; frame-src 'self';"
},
"default_locale": "en"
}
2 changes: 1 addition & 1 deletion add-on/manifest.firefox.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"browser_action": {
"action": {
"browser_style": false
},
"options_ui": {
Expand Down
5 changes: 0 additions & 5 deletions add-on/src/background/background.html

This file was deleted.

1 change: 0 additions & 1 deletion add-on/src/background/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ 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) {
Expand Down
4 changes: 3 additions & 1 deletion add-on/src/landing-pages/welcome/store.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict'
/* eslint-env browser, webextensions */
import browser from 'webextension-polyfill'
import { handleConsentFromState, trackView } from '../../lib/telemetry.js'

export default function createWelcomePageStore (i18n, runtime) {
return function welcomePageStore (state, emitter) {
Expand All @@ -9,7 +10,8 @@ export default function createWelcomePageStore (i18n, runtime) {
state.webuiRootUrl = null
let port
emitter.on('DOMContentLoaded', async () => {
browser.runtime.sendMessage({ telemetry: { trackView: 'welcome' } })
handleConsentFromState(state)
trackView('welcome')
emitter.emit('render')
port = runtime.connect({ name: 'browser-action-port' })
port.onMessage.addListener(async (message) => {
Expand Down
41 changes: 6 additions & 35 deletions add-on/src/lib/context-menus.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,29 +71,13 @@ export function createContextMenus (
getState, _runtime, ipfsPathValidator, { onAddFromContext, onCopyRawCid, onCopyAddressAtPublicGw }) {
try {
const createSubmenu = (id, contextType, menuBuilder) => {
browser.contextMenus.create({
id,
title: browser.i18n.getMessage(id),
documentUrlPatterns: ['<all_urls>'],
contexts: [contextType]
})
browser.contextMenus.onClicked.addListener((...args) => console.log(args))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we still need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be gone.

}
const createImportToIpfsMenuItem = (parentId, id, contextType, ipfsAddOptions) => {
const itemId = `${parentId}_${id}`
apiMenuItems.add(itemId)
return browser.contextMenus.create({
id: itemId,
parentId,
title: browser.i18n.getMessage(id),
contexts: [contextType],
documentUrlPatterns: ['<all_urls>'],
enabled: false,
/* no support for 'icons' in Chrome
icons: {
'48': '/ui-kit/icons/stroke_cube.svg'
}, */
onclick: (context) => onAddFromContext(context, contextType, ipfsAddOptions)
})
return browser.contextMenus.onClicked.addListener((context) => onAddFromContext(context, contextType, ipfsAddOptions)
)
}
const createCopierMenuItem = (parentId, id, contextType, handler) => {
const itemId = `${parentId}_${id}`
Expand All @@ -102,22 +86,9 @@ export function createContextMenus (
if (apiMenuItemIds.has(id)) {
apiMenuItems.add(itemId)
}
return browser.contextMenus.create({
id: itemId,
parentId,
title: browser.i18n.getMessage(id),
contexts: [contextType],
documentUrlPatterns: [
'*://*/ipfs/*', '*://*/ipns/*',
'*://*.ipfs.dweb.link/*', '*://*.ipns.dweb.link/*', // TODO: add any custom public gateway from Preferences
'*://*.ipfs.localhost/*', '*://*.ipns.localhost/*'
],
/* no support for 'icons' in Chrome
icons: {
'48': '/ui-kit/icons/stroke_copy.svg'
}, */
onclick: (context) => handler(context, contextType)
})
return browser.contextMenus.onClicked.addListener(
(context) => handler(context, contextType)
)
}
const buildSubmenu = (parentId, contextType) => {
createSubmenu(parentId, contextType)
Expand Down
37 changes: 11 additions & 26 deletions add-on/src/lib/ipfs-companion.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ 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 Down Expand Up @@ -57,11 +56,8 @@ 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 @@ -108,7 +104,7 @@ export default async function init () {
throw new Error('IPFS Companion: API client is disabled')
}

function registerListeners () {
function registerListeners() {
const onBeforeSendInfoSpec = ['blocking', 'requestHeaders']
if (browser.webRequest.OnBeforeSendHeadersOptions && 'EXTRA_HEADERS' in browser.webRequest.OnBeforeSendHeadersOptions) {
// Chrome 72+ requires 'extraHeaders' for accessing all headers
Expand Down Expand Up @@ -172,16 +168,6 @@ 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) {
const { version } = browser.runtime.getManifest()
return trackView(request.trackView, { version })
}
}

// PORTS (connection-based messaging)
Expand Down Expand Up @@ -473,7 +459,7 @@ export default async function init () {
// -------------------------------------------------------------------

async function updateBrowserActionBadge () {
if (typeof browser.browserAction.setBadgeBackgroundColor === 'undefined') {
if (typeof browser.action.setBadgeBackgroundColor === 'undefined') {
// Firefox for Android does not have this UI, so we just skip it
return
}
Comment on lines +466 to 469
Copy link
Member

@SgtPooki SgtPooki Mar 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this is supported in firefox for android now, https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/action/setBadgeBackgroundColor#browser_compatibility, so we can remove the comment, but we may still want to double check for the existence of this method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the weird part is, firefox for android limited the number of extensions, there was IIRC a brief moment in time to get this running, but not anymore for the foreseeable future.

I replaced a regex, so could be removed, I'd rather not touch it rn.

Expand All @@ -498,13 +484,13 @@ export default async function init () {
badgeIcon = '/icons/ipfs-logo-off.svg'
}
try {
const oldColor = colorArraytoHex(await browser.browserAction.getBadgeBackgroundColor({}))
const oldColor = colorArraytoHex(await browser.action.getBadgeBackgroundColor({}))
if (badgeColor !== oldColor) {
await browser.browserAction.setBadgeBackgroundColor({ color: badgeColor })
await browser.action.setBadgeBackgroundColor({ color: badgeColor })
await setBrowserActionIcon(badgeIcon)
}
const oldText = await browser.browserAction.getBadgeText({})
if (oldText !== badgeText) await browser.browserAction.setBadgeText({ text: badgeText })
const oldText = await browser.action.getBadgeText({})
if (oldText !== badgeText) await browser.action.setBadgeText({ text: badgeText })
} catch (error) {
console.error('Unable to update browserAction badge due to error', error)
}
Expand All @@ -525,14 +511,17 @@ export default async function init () {
let iconDefinition = { path: iconPath }
try {
// Try SVG first -- Firefox supports it natively
await browser.browserAction.setIcon(iconDefinition)
await browser.action.setIcon(iconDefinition)
if (browser.runtime.lastError.message === 'Icon invalid.') {
throw new Error('Icon invalid.')
whizzzkid marked this conversation as resolved.
Show resolved Hide resolved
}
} catch (error) {
// Fallback!
// Chromium does not support SVG [ticket below is 8 years old, I can't even..]
// https://bugs.chromium.org/p/chromium/issues/detail?id=29683
// Still, we want icon, so we precompute rasters of popular sizes and use them instead
iconDefinition = await rasterIconDefinition(iconPath)
await browser.browserAction.setIcon(iconDefinition)
await browser.action.setIcon(iconDefinition)
}
}

Expand Down Expand Up @@ -568,8 +557,6 @@ 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 @@ -636,8 +623,6 @@ 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: 3 additions & 1 deletion add-on/src/lib/telemetry.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import browser from 'webextension-polyfill'
import MetricsProvider from '@ipfs-shipyard/ignite-metrics/vanilla'
import debug from 'debug'

Expand Down Expand Up @@ -36,7 +37,8 @@ export function handleConsentFromState (state) {
const ignoredViewsRegex = []
export function trackView (view, segments) {
log('trackView called for view: ', view)
metricsProvider.trackView(view, ignoredViewsRegex, segments)
const { version } = browser.runtime.getManifest()
metricsProvider.trackView(view, ignoredViewsRegex, { ...segments, version })
}

export const startSession = (...args) => metricsProvider.startSession(...args)
Expand Down
4 changes: 3 additions & 1 deletion add-on/src/options/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import browser from 'webextension-polyfill'
import { optionDefaults } from '../lib/options.js'
import createRuntimeChecks from '../lib/runtime-checks.js'
import { handleConsentFromState, trackView } from '../lib/telemetry.js'

// The store contains and mutates the state for the app
export default function optionStore (state, emitter) {
Expand All @@ -20,7 +21,8 @@ export default function optionStore (state, emitter) {
}

emitter.on('DOMContentLoaded', async () => {
browser.runtime.sendMessage({ telemetry: { trackView: 'options' } })
handleConsentFromState(state)
trackView('options')
updateStateOptions()
browser.storage.onChanged.addListener(updateStateOptions)
})
Expand Down
7 changes: 6 additions & 1 deletion add-on/src/popup/browser-action/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { browserActionFilesCpImportCurrentTab } from '../../lib/ipfs-import.js'
import { ipfsContentPath } from '../../lib/ipfs-path.js'
import { welcomePage, optionsPage } from '../../lib/constants.js'
import { contextMenuViewOnGateway, contextMenuCopyAddressAtPublicGw, contextMenuCopyPermalink, contextMenuCopyRawCid, contextMenuCopyCanonicalAddress, contextMenuCopyCidAddress } from '../../lib/context-menus.js'
import { endSession, handleConsentFromState, startSession, trackView } from '../../lib/telemetry.js'

// The store contains and mutates the state for the app
export default (state, emitter) => {
Expand Down Expand Up @@ -38,7 +39,8 @@ export default (state, emitter) => {
let port

emitter.on('DOMContentLoaded', async () => {
browser.runtime.sendMessage({ telemetry: { trackView: 'browser-action' } })
handleConsentFromState(state)
trackView('browser-action')

// initial render with status stub
emitter.emit('render')
Expand Down Expand Up @@ -205,6 +207,7 @@ export default (state, emitter) => {
const prev = state.active
state.active = !prev
if (!state.active) {
endSession()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we're only calling startSession and endSession inside this DOMContentLoaded for browser-action now, then I don't think we're going to be tracking sessions properly anymore. Is that blocked by ipfs-shipyard/ignite-metrics#103 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm.. that can be revisited, this was to just make sure we didn't miss out on these events, but these or on the best effort basis.

state.gatewayAddress = state.pubGwURLString
state.ipfsApiUrl = null
state.gatewayVersion = null
Expand All @@ -213,6 +216,8 @@ export default (state, emitter) => {
}
try {
await browser.storage.local.set({ active: state.active })
startSession()
handleConsentFromState(state)
} catch (error) {
console.error(`Unable to update global Active flag due to ${error}`)
state.active = prev
Expand Down
4 changes: 3 additions & 1 deletion add-on/src/popup/quick-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { formatImportDirectory } from '../lib/ipfs-import.js'
import all from 'it-all'
import drop from 'drag-and-drop-files'
import { filesize } from 'filesize'
import { handleConsentFromState, trackView } from '../lib/telemetry.js'

document.title = browser.i18n.getMessage('quickImport_page_title')

Expand Down Expand Up @@ -48,7 +49,8 @@ function quickImportStore (state, emitter) {
let port

emitter.on('DOMContentLoaded', async () => {
browser.runtime.sendMessage({ telemetry: { trackView: 'quick-import' } })
handleConsentFromState(state)
trackView('quick-import')
// initialize connection to the background script which will trigger UI updates
port = browser.runtime.connect({ name: 'browser-action-port' })
port.onMessage.addListener(async (message) => {
Expand Down
4 changes: 3 additions & 1 deletion add-on/src/recovery/recovery.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import browser, { i18n, runtime } from 'webextension-polyfill'
import { nodeOffSvg } from '../landing-pages/welcome/page.js'
import createWelcomePageStore from '../landing-pages/welcome/store.js'
import { optionsPage } from '../lib/constants.js'
import { handleConsentFromState, trackView } from '../lib/telemetry.js'
import './recovery.css'

const app = choo()
Expand All @@ -19,7 +20,8 @@ const optionsPageLink = html`<a class="navy link underline-under hover-aqua" id=
app.use(createWelcomePageStore(i18n, runtime))
// Register our single route
app.route('*', (state) => {
browser.runtime.sendMessage({ telemetry: { trackView: 'recovery' } })
handleConsentFromState(state)
trackView('recovery')
const { hash } = window.location
const { href: publicURI } = new URL(decodeURIComponent(hash.slice(1)))

Expand Down
Loading