|
|
|
import {v4 as uuidv4} from 'uuid';
|
|
|
|
|
|
|
|
import storage from 'storage/storage';
|
|
|
|
import {
|
|
|
|
getText,
|
|
|
|
getActiveTab,
|
|
|
|
getPlatform,
|
|
|
|
getDayPrecisionEpoch,
|
|
|
|
getRandomInt,
|
|
|
|
sleep
|
|
|
|
} from 'utils/common';
|
|
|
|
import {targetEnv, enableContributions} from 'utils/config';
|
|
|
|
|
|
|
|
async function showNotification({
|
|
|
|
message,
|
|
|
|
messageId,
|
|
|
|
title,
|
|
|
|
type = 'info',
|
|
|
|
timeout = 0
|
|
|
|
}) {
|
|
|
|
if (!title) {
|
|
|
|
title = getText('extensionName');
|
|
|
|
}
|
|
|
|
if (messageId) {
|
|
|
|
message = getText(messageId);
|
|
|
|
}
|
|
|
|
const notification = await browser.notifications.create(
|
|
|
|
`bc-notification-${type}`,
|
|
|
|
{
|
|
|
|
type: 'basic',
|
|
|
|
title,
|
|
|
|
message,
|
|
|
|
iconUrl: '/src/assets/icons/app/icon-64.png'
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
if (timeout) {
|
|
|
|
window.setTimeout(() => {
|
|
|
|
browser.notifications.clear(notification);
|
|
|
|
}, timeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
return notification;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getListItems(data, {scope = ''} = {}) {
|
|
|
|
const labels = {};
|
|
|
|
for (const [group, items] of Object.entries(data)) {
|
|
|
|
labels[group] = [];
|
|
|
|
items.forEach(function (value) {
|
|
|
|
const item = {
|
|
|
|
value,
|
|
|
|
title: getText(`${scope ? scope + '_' : ''}${value}`)
|
|
|
|
};
|
|
|
|
|
|
|
|
labels[group].push(item);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return labels;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function configApp(app) {
|
|
|
|
const platform = await getPlatform();
|
|
|
|
|
|
|
|
document.documentElement.classList.add(platform.targetEnv, platform.os);
|
|
|
|
|
|
|
|
if (app) {
|
|
|
|
app.config.globalProperties.$env = platform;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function loadFonts(fonts) {
|
|
|
|
await Promise.allSettled(fonts.map(font => document.fonts.load(font)));
|
|
|
|
}
|
|
|
|
|
|
|
|
function processMessageResponse(response, sendResponse) {
|
|
|
|
if (targetEnv === 'safari') {
|
|
|
|
response.then(function (result) {
|
|
|
|
// Safari 15: undefined response will cause sendMessage to never resolve.
|
|
|
|
if (result === undefined) {
|
|
|
|
result = null;
|
|
|
|
}
|
|
|
|
sendResponse(result);
|
|
|
|
});
|
|
|
|
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function showContributePage({
|
|
|
|
action = '',
|
|
|
|
activeTab = null,
|
|
|
|
setOpenerTab = true,
|
|
|
|
updateStats = true
|
|
|
|
} = {}) {
|
|
|
|
if (updateStats) {
|
|
|
|
await storage.set({contribPageLastOpen: getDayPrecisionEpoch()});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!activeTab) {
|
|
|
|
activeTab = await getActiveTab();
|
|
|
|
}
|
|
|
|
let url = browser.runtime.getURL('/src/contribute/index.html');
|
|
|
|
if (action) {
|
|
|
|
url = `${url}?action=${action}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
const props = {url, index: activeTab.index + 1, active: true};
|
|
|
|
|
|
|
|
if (
|
|
|
|
setOpenerTab &&
|
|
|
|
activeTab.id !== browser.tabs.TAB_ID_NONE &&
|
|
|
|
(await getPlatform()).os !== 'android'
|
|
|
|
) {
|
|
|
|
props.openerTabId = activeTab.id;
|
|
|
|
}
|
|
|
|
|
|
|
|
return browser.tabs.create(props);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function autoShowContributePage({
|
|
|
|
minUseCount = 0, // 0-1000
|
|
|
|
minInstallDays = 0,
|
|
|
|
minLastOpenDays = 0,
|
|
|
|
minLastAutoOpenDays = 0,
|
|
|
|
activeTab = null,
|
|
|
|
setOpenerTab = true,
|
|
|
|
action = 'auto'
|
|
|
|
} = {}) {
|
|
|
|
if (enableContributions) {
|
|
|
|
const options = await storage.get([
|
|
|
|
'showContribPage',
|
|
|
|
'useCount',
|
|
|
|
'installTime',
|
|
|
|
'contribPageLastOpen',
|
|
|
|
'contribPageLastAutoOpen'
|
|
|
|
]);
|
|
|
|
|
|
|
|
const epoch = getDayPrecisionEpoch();
|
|
|
|
|
|
|
|
if (
|
|
|
|
options.showContribPage &&
|
|
|
|
options.useCount >= minUseCount &&
|
|
|
|
epoch - options.installTime >= minInstallDays * 86400000 &&
|
|
|
|
epoch - options.contribPageLastOpen >= minLastOpenDays * 86400000 &&
|
|
|
|
epoch - options.contribPageLastAutoOpen >= minLastAutoOpenDays * 86400000
|
|
|
|
) {
|
|
|
|
await storage.set({
|
|
|
|
contribPageLastOpen: epoch,
|
|
|
|
contribPageLastAutoOpen: epoch
|
|
|
|
});
|
|
|
|
|
|
|
|
return showContributePage({
|
|
|
|
action,
|
|
|
|
activeTab,
|
|
|
|
setOpenerTab,
|
|
|
|
updateStats: false
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let useCountLastUpdate = 0;
|
|
|
|
async function updateUseCount({
|
|
|
|
valueChange = 1,
|
|
|
|
maxUseCount = Infinity,
|
|
|
|
minInterval = 0
|
|
|
|
} = {}) {
|
|
|
|
if (Date.now() - useCountLastUpdate >= minInterval) {
|
|
|
|
useCountLastUpdate = Date.now();
|
|
|
|
|
|
|
|
const {useCount} = await storage.get('useCount');
|
|
|
|
|
|
|
|
if (useCount < maxUseCount) {
|
|
|
|
await storage.set({useCount: useCount + valueChange});
|
|
|
|
} else if (useCount > maxUseCount) {
|
|
|
|
await storage.set({useCount: maxUseCount});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function processAppUse({
|
|
|
|
activeTab = null,
|
|
|
|
setOpenerTab = true,
|
|
|
|
action = 'auto'
|
|
|
|
} = {}) {
|
|
|
|
await updateUseCount({
|
|
|
|
valueChange: 1,
|
|
|
|
maxUseCount: 1000
|
|
|
|
});
|
|
|
|
|
|
|
|
return autoShowContributePage({
|
|
|
|
minUseCount: 10,
|
|
|
|
minInstallDays: 14,
|
|
|
|
minLastOpenDays: 14,
|
|
|
|
minLastAutoOpenDays: 365,
|
|
|
|
activeTab,
|
|
|
|
setOpenerTab,
|
|
|
|
action
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function meanSleep(ms) {
|
|
|
|
const maxDeviation = 0.1 * ms;
|
|
|
|
return sleep(getRandomInt(ms - maxDeviation, ms + maxDeviation));
|
|
|
|
}
|
|
|
|
|
|
|
|
function sendNativeMessage(port, message, {timeout = 10000} = {}) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const id = uuidv4();
|
|
|
|
message.id = id;
|
|
|
|
|
|
|
|
const messageCallback = function (msg) {
|
|
|
|
if (msg.id !== id) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
removeListeners();
|
|
|
|
resolve(msg);
|
|
|
|
};
|
|
|
|
const errorCallback = function () {
|
|
|
|
removeListeners();
|
|
|
|
reject('No response from native app');
|
|
|
|
};
|
|
|
|
const removeListeners = function () {
|
|
|
|
window.clearTimeout(timeoutId);
|
|
|
|
port.onMessage.removeListener(messageCallback);
|
|
|
|
port.onDisconnect.removeListener(errorCallback);
|
|
|
|
};
|
|
|
|
|
|
|
|
const timeoutId = window.setTimeout(function () {
|
|
|
|
errorCallback();
|
|
|
|
}, timeout);
|
|
|
|
|
|
|
|
port.onMessage.addListener(messageCallback);
|
|
|
|
port.onDisconnect.addListener(errorCallback);
|
|
|
|
|
|
|
|
port.postMessage(message);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async function pingClientApp({
|
|
|
|
start = true,
|
|
|
|
stop = true,
|
|
|
|
checkResponse = true
|
|
|
|
} = {}) {
|
|
|
|
if (start) {
|
|
|
|
await browser.runtime.sendMessage({id: 'startClientApp'});
|
|
|
|
}
|
|
|
|
|
|
|
|
const rsp = await browser.runtime.sendMessage({
|
|
|
|
id: 'messageClientApp',
|
|
|
|
message: {command: 'ping'}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (stop) {
|
|
|
|
await browser.runtime.sendMessage({id: 'stopClientApp'});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (checkResponse && (!rsp.success || rsp.data !== 'pong')) {
|
|
|
|
throw new Error(`Client app response: ${rsp.data}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rsp;
|
|
|
|
}
|
|
|
|
|
|
|
|
export {
|
|
|
|
showNotification,
|
|
|
|
getListItems,
|
|
|
|
configApp,
|
|
|
|
loadFonts,
|
|
|
|
processMessageResponse,
|
|
|
|
showContributePage,
|
|
|
|
autoShowContributePage,
|
|
|
|
updateUseCount,
|
|
|
|
processAppUse,
|
|
|
|
meanSleep,
|
|
|
|
sendNativeMessage,
|
|
|
|
pingClientApp
|
|
|
|
};
|