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

Add Firefox support #1154

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
/examples/*/inboxsdk.js*
/examples/*/pageWorld.js*
/examples/*/background.js*
/examples/*/firefox.js*
/examples/*/platform-implementation.js*
*~
npm-debug.log
Expand All @@ -14,6 +15,7 @@ yarn-debug.log
/.inboxsdk_test_secret
yarn-error.log
/packages/core/inboxsdk.js*
/packages/core/firefox.js*
/packages/core/inboxsdk.min.js
/packages/core/pageWorld.js*
/packages/core/platform-implementation.js*
Expand Down
24 changes: 24 additions & 0 deletions examples/firefox-mv3/content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

console.log('InboxSDK: content script loaded');

InboxSDK.load(2, 'firefox-mv3-example', {
appName: 'Twitter',
appIconUrl:
'http://materialdesignblog.com/wp-content/uploads/2015/04/387b93c8b66379c32e1cc2b98dcf5197.png',
suppressAddonTitle: 'Streak',
}).then((sdk) => {
console.log('InboxSDK: has loaded', sdk);

sdk.Compose.registerComposeViewHandler((view) => {
view.addButton({
title: 'My button',
iconUrl: (globalThis.chrome || globalThis.browser).runtime.getURL(
'monkey.png',
),
onClick() {
alert('my button works!');
},
});
});
});
24 changes: 24 additions & 0 deletions examples/firefox-mv3/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"manifest_version": 3,
"name": "Firefox MV3",
"description": "InboxSDK example!",
"version": "1.0",
"host_permissions": ["https://mail.google.com/"],
"permissions": ["scripting"],
"background": {
"scripts": ["background.js"]
},
"content_scripts": [
{
"matches": ["*://mail.google.com/*"],
"js": ["firefox.js", "content.js"],
"run_at": "document_end"
}
],
"web_accessible_resources": [
{
"matches": ["*://mail.google.com/*"],
"resources": ["pageWorld.js", "monkey.png"]
}
]
}
Binary file added examples/firefox-mv3/monkey.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 2 additions & 4 deletions examples/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { InboxSDK as InboxSDK_ } from '../src/inboxsdk';
import * as SDK from '../src/inboxsdk';

declare global {
var InboxSDK: {
load(version: string, moduleName: string): Promise<InboxSDK_>;
};
var InboxSDK: typeof SDK;
}
10 changes: 10 additions & 0 deletions gulpfile.babel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ async function setupExamples() {
'./packages/core/inboxsdk.js',
'./packages/core/pageWorld.js',
'./packages/core/background.js',
'./packages/core/firefox.js',
];

dirs = dirs.concat(await fg(['examples/*'], { onlyDirectories: true }));
Expand Down Expand Up @@ -304,6 +305,7 @@ gulp.task('clean', async () => {

const outputFiles = [
'./packages/core/inboxsdk.js',
'./packages/core/firefox.js',
'./packages/core/platform-implementation.js',
'./packages/core/pageWorld.js',
];
Expand Down Expand Up @@ -397,6 +399,14 @@ if (args.remote) {
},
import: './src/inboxsdk-js/inboxsdk-NONREMOTE',
},
firefox: {
library: {
export: 'default',
name: 'InboxSDK',
type: OutputLibraryType.UMD,
},
import: './src/inboxsdk-js/inboxsdk-FIREFOX',
},
DaPotatoMan marked this conversation as resolved.
Show resolved Hide resolved
},
disableMinification: true,
afterBuild: async () => {
Expand Down
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module.exports = {
globals: {
SDK_VERSION: 'beep',
},
setupFiles: ['./jest.setup.js'],
moduleNameMapper: {
'\\.css$': require.resolve('jest-css-modules'),
},
Expand Down
8 changes: 8 additions & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Adds support for `chrome` in unit tests
if (!globalThis.chrome) {
globalThis.chrome = {
runtime: {
id: 'testid',
},
};
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@babel/register": "^7.22.15",
"@macil/simple-base-converter": "^1.0.0",
"@types/asap": "^2.0.0",
"@types/chrome": "^0.0.263",
"@types/classnames": "^2.2.8",
"@types/gulp": "^4.0.6",
"@types/http-server": "^0.10.1",
Expand All @@ -26,6 +27,7 @@
"@types/sinon-test": "^2.4.0",
"@types/transducers.js": "^0.3.0",
"@types/vinyl-source-stream": "^0.0.30",
"@types/webextension-polyfill": "^0.10.7",
"@typescript-eslint/eslint-plugin": "^5.59.8",
"@typescript-eslint/parser": "^5.59.8",
"asap": "^2.0.3",
Expand Down
20 changes: 15 additions & 5 deletions packages/core/background.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
/* global chrome */
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'inboxsdk__injectPageWorld' && sender.tab) {
if (chrome.scripting) {
/** @type {import('webextension-polyfill').Browser} */
const browser = globalThis.chrome || globalThis.browser;
const isFirefox = 'browser' in globalThis;
DaPotatoMan marked this conversation as resolved.
Show resolved Hide resolved

browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
const eventKey = 'inboxsdk__injectPageWorld';

if (message.type === eventKey && sender.tab) {
// Relay event to firefox content script handler
if (isFirefox) {
return browser.tabs.sendMessage(sender.tab.id, { type: eventKey });
}

if (browser.scripting) {
// MV3
chrome.scripting.executeScript({
browser.scripting.executeScript({
target: { tabId: sender.tab.id },
world: 'MAIN',
files: ['pageWorld.js'],
Expand Down
1 change: 1 addition & 0 deletions packages/core/firefox.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {};
14 changes: 14 additions & 0 deletions src/common/extension-apis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/** Refers to {@link chrome} or `browser` in firefox */
export const browser = globalThis.chrome || (globalThis as any).browser;

if (!browser) {
throw new Error('chrome or browser not available in current context.');
}

export function getExtensionId() {
try {
return browser.runtime.id;
wegry marked this conversation as resolved.
Show resolved Hide resolved
} catch (error) {
console.error('Failed to get extension id', error);
}
}
7 changes: 0 additions & 7 deletions src/common/get-extension-id.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/common/log-error.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import ajax from './ajax';
import rateLimit from './rate-limit';
import getStackTrace from './get-stack-trace';
import getExtensionId from './get-extension-id';
import getSessionId from './get-session-id';
import { BUILD_VERSION } from './version';
import isObject from 'lodash/isObject';
import { getExtensionId } from './extension-apis';

export interface LogErrorContext {
appId?: string;
Expand Down
21 changes: 21 additions & 0 deletions src/inboxsdk-js/inboxsdk-FIREFOX.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { browser } from '../common/extension-apis';
import InboxSDK from './inboxsdk-NONREMOTE';

browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'inboxsdk__injectPageWorld') {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = browser.runtime.getURL('pageWorld.js');

script.onload = () => console.log('InboxSDK: pageWorld.js has been loaded');
script.onerror = (error) =>
console.error('InboxSDK: pageWorld.js failed to load\n\n', error);

document.head.appendChild(script);

console.log('InboxSDK: pageWorld.js manually injected!');
sendResponse(true);
}
});

export default InboxSDK;
39 changes: 20 additions & 19 deletions src/platform-implementation-js/lib/inject-script.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
import once from 'lodash/once';
import Kefir from 'kefir';
import makeMutationObserverChunkedStream from './dom/make-mutation-observer-chunked-stream';
import { browser } from '../../common/extension-apis';

declare global {
/** Set by webpack.DefinePlugin to remove a code block potentially flagged by Chrome Web Store reviews. */
var NPM_MV2_SUPPORT: false | undefined;
}

let injectScriptImplementation: () => void = () => {
(window as any).chrome.runtime.sendMessage(
{ type: 'inboxsdk__injectPageWorld' },
(didExecute: boolean) => {
if (!didExecute) {
if (NPM_MV2_SUPPORT) {
// MV2 support.
// Removed from regular MV3 NPM builds to not falsely set off Chrome Web Store
// about dynamically-loaded code.
const scr = document.createElement('script');
scr.type = 'text/javascript';
scr.src = (window as any).chrome.runtime.getURL('pageWorld.js');
document.documentElement.appendChild(scr);
} else {
throw new Error(
"Couldn't inject pageWorld.js. Check that the extension is using MV3 and has the correct permissions and host_permissions in its manifest.",
);
}
browser.runtime
.sendMessage({ type: 'inboxsdk__injectPageWorld' })
.then((didExecute: boolean) => {
/** pageWorld was injected successfully */
if (didExecute) return;

if (NPM_MV2_SUPPORT) {
// MV2 support.
// Removed from regular MV3 NPM builds to not falsely set off Chrome Web Store
// about dynamically-loaded code.
const scr = document.createElement('script');
scr.type = 'text/javascript';
scr.src = browser.runtime.getURL('pageWorld.js');
document.documentElement.appendChild(scr);
} else {
throw new Error(
"Couldn't inject pageWorld.js. Check that the extension is using MV3 and has the correct permissions and host_permissions in its manifest.",
);
}
},
);
});
};

// Returns a promise that resolves once the injected script has been injected
Expand Down
2 changes: 1 addition & 1 deletion src/platform-implementation-js/lib/logger.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Sha256 from 'sha.js/sha256';
import ajax from '../../common/ajax';
import getExtensionId from '../../common/get-extension-id';
import { getExtensionId } from '../../common/extension-apis';
import getSessionId from '../../common/get-session-id';
import logError from '../../common/log-error';
import PersistentQueue from './persistent-queue';
Expand Down
42 changes: 42 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2192,6 +2192,16 @@ __metadata:
languageName: node
linkType: hard

"@types/chrome@npm:^0.0.263":
version: 0.0.263
resolution: "@types/chrome@npm:0.0.263"
dependencies:
"@types/filesystem": "npm:*"
"@types/har-format": "npm:*"
checksum: ecffc799c2c8df057457fb79be0ac69d2d3327e53343d6727f1633a4782037613e7392ede6541284a9fa771d4d63352d61889882328c95e0ea9af4c65d8c98c5
languageName: node
linkType: hard

"@types/classnames@npm:^2.2.8":
version: 2.3.1
resolution: "@types/classnames@npm:2.3.1"
Expand Down Expand Up @@ -2251,6 +2261,22 @@ __metadata:
languageName: node
linkType: hard

"@types/filesystem@npm:*":
version: 0.0.36
resolution: "@types/filesystem@npm:0.0.36"
dependencies:
"@types/filewriter": "npm:*"
checksum: ec831040fe3aff066ffb7b7541e21a5dd59aa06e7175c61e592736e38b018b1d513551438254631e2a3fbc81ff671bf618401000f4c8ea79156934cbc7dcaeaa
languageName: node
linkType: hard

"@types/filewriter@npm:*":
version: 0.0.33
resolution: "@types/filewriter@npm:0.0.33"
checksum: 495a4bb424c27eda967fe9ac3b8f7b781e6b3f9ce59403a991590cb1073022f9c5383d3c7d808ef6956b785550c36664c4fcd502dc0baf69e340bd481171e0ca
languageName: node
linkType: hard

"@types/glob-stream@npm:*":
version: 6.1.0
resolution: "@types/glob-stream@npm:6.1.0"
Expand Down Expand Up @@ -2291,6 +2317,13 @@ __metadata:
languageName: node
linkType: hard

"@types/har-format@npm:*":
version: 1.2.15
resolution: "@types/har-format@npm:1.2.15"
checksum: fcb397741076ed1095ef8dcccd408c9ef4e20fcfeef0d3fe700f837cc015fe72ee2a3c081cc9c03d73c115005b38ba7b1c563d27e050fa612d60bc2049f309ca
languageName: node
linkType: hard

"@types/http-server@npm:^0.10.1":
version: 0.10.1
resolution: "@types/http-server@npm:0.10.1"
Expand Down Expand Up @@ -2548,6 +2581,13 @@ __metadata:
languageName: node
linkType: hard

"@types/webextension-polyfill@npm:^0.10.7":
version: 0.10.7
resolution: "@types/webextension-polyfill@npm:0.10.7"
checksum: b2d9b4f4e7ebafb299e8dfc75ac2c3cc6cc29bebce5f590c01533388b0d22d4a9fa8e7cb547cb69d1092c3a8d573675df3355d46c64de77d690b30ae641eeb77
languageName: node
linkType: hard

"@types/yargs-parser@npm:*":
version: 20.2.0
resolution: "@types/yargs-parser@npm:20.2.0"
Expand Down Expand Up @@ -6708,6 +6748,7 @@ __metadata:
"@babel/register": "npm:^7.22.15"
"@macil/simple-base-converter": "npm:^1.0.0"
"@types/asap": "npm:^2.0.0"
"@types/chrome": "npm:^0.0.263"
"@types/classnames": "npm:^2.2.8"
"@types/gulp": "npm:^4.0.6"
"@types/http-server": "npm:^0.10.1"
Expand All @@ -6722,6 +6763,7 @@ __metadata:
"@types/sinon-test": "npm:^2.4.0"
"@types/transducers.js": "npm:^0.3.0"
"@types/vinyl-source-stream": "npm:^0.0.30"
"@types/webextension-polyfill": "npm:^0.10.7"
"@typescript-eslint/eslint-plugin": "npm:^5.59.8"
"@typescript-eslint/parser": "npm:^5.59.8"
asap: "npm:^2.0.3"
Expand Down