Skip to content

Commit

Permalink
WIP sync
Browse files Browse the repository at this point in the history
  • Loading branch information
tophf committed Oct 21, 2024
1 parent 6b3efcc commit 9e4eb44
Show file tree
Hide file tree
Showing 31 changed files with 287 additions and 229 deletions.
8 changes: 8 additions & 0 deletions src/background-sw/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// WARNING! /background must be the first to set global.API
import '/background';
import {cloudDrive} from '/background/db-to-cloud-broker';
import {API, _execute} from '/js/msg';
import {createPortProxy, initRemotePort} from '/js/port';
import {workerPath, ownRoot} from '/js/urls';
Expand Down Expand Up @@ -42,3 +43,10 @@ self.onfetch = evt => {
}

API.worker = createPortProxy(() => offscreen.getWorkerPort(workerPath), workerPath);

cloudDrive.webdav = async cfg => {
const res = await offscreen.webdavInit(cfg);
const webdav = offscreen.webdav;
for (const k in res) res[k] ??= webdav.bind(null, k);
return res;
};
6 changes: 4 additions & 2 deletions src/background-sw/set-client-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {isVivaldi} from '/background/common';
import prefsApi from '/background/prefs-api';
import * as styleMan from '/background/style-manager';
import * as syncMan from '/background/sync-manager';
import {API} from '/js/msg-base';
import {API} from '/js/msg-api';
import * as prefs from '/js/prefs';
import {FIREFOX} from '/js/ua';

Expand Down Expand Up @@ -54,5 +54,7 @@ export default async function setClientData(evt, reqUrl) {

v = await Promise.all(Object.values(jobs));
Object.keys(jobs).forEach((id, i) => (jobs[id] = v[i]));
return new Response(`var clientData = ${JSON.stringify(jobs)}`, NO_CACHE);
return new Response(`var clientData = new Proxy(${JSON.stringify(jobs)}, {get: ${(obj, k, _) => ((
(_ = obj[k]), delete obj[k], _
))}})`, NO_CACHE);
}
7 changes: 7 additions & 0 deletions src/background/db-to-cloud-broker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import dropbox from 'db-to-cloud/lib/drive/dropbox';
import onedrive from 'db-to-cloud/lib/drive/onedrive';
import google from 'db-to-cloud/lib/drive/google';
import webdav from 'db-to-cloud/lib/drive/webdav';

export const cloudDrive = {dropbox, onedrive, google, webdav: !process.env.MV3 && webdav};
export {dbToCloud} from 'db-to-cloud/lib/db-to-cloud';
7 changes: 3 additions & 4 deletions src/background/download.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {kAppUrlencoded, kContentType} from '/js/consts';
import {tryJSONparse, URLS} from '/js/toolbox';

/** @type {Record<string, {req: Promise, ports: Set<chrome.runtime.Port>}>} */
Expand Down Expand Up @@ -63,9 +64,7 @@ async function doDownload(url, {
} else if (method === 'GET' && url.length >= 2000 && url.startsWith(URLS.usoJson)) {
url = collapseUsoVars(usoVars = [], url, i);
}
headers ??= {
'Content-type': 'application/x-www-form-urlencoded',
};
headers ??= {[kContentType]: kAppUrlencoded};
}
/** @type {Response | XMLHttpRequest} */
const resp = process.env.MV3
Expand Down Expand Up @@ -193,7 +192,7 @@ async function fetchWithProgress(resp, responseType, headers, jobKey) {
}
}
if (responseType === 'blob') {
data = new Blob([data], {type: headers.get('Content-Type')});
data = new Blob([data], {type: headers.get(kContentType)});
} else if (responseType === 'arraybuffer') {
data = data.buffer;
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/background/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import * as usercssMan from './usercss-manager';
import * as usoApi from './uso-api';
import * as uswApi from './usw-api';

Object.assign(API, /** @namespace API */ {
Object.assign(API, {

//#region API data/db/info

Expand Down
2 changes: 1 addition & 1 deletion src/background/intro.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
global.API = {}; // will be used by msg.js
process.env.API = {}; // `global.API` will be used by msg.js
47 changes: 24 additions & 23 deletions src/background/sync-manager.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import browser from '/js/browser';
import {kGetAccessToken} from '/js/consts';
import {API} from '/js/msg-api';
import * as prefs from '/js/prefs';
import {chromeLocal, chromeSync} from '/js/storage-util';
import {fetchWebDAV, hasOwn} from '/js/util-base';
import {broadcastExtension} from './broadcast';
import {bgReady, uuidIndex} from './common';
import db from './db';
import {cloudDrive, dbToCloud} from './db-to-cloud-broker';
import {overrideBadge} from './icon-manager';
import * as styleMan from './style-manager';
import {getToken, revokeToken} from './token-manager';
import * as dbToCloud from 'db-to-cloud';

//#region Init

Expand Down Expand Up @@ -99,11 +102,12 @@ export async function getDriveOptions(driveName) {

export async function start(name, fromPref = false) {
if (ready.then) await ready;
if (!ctrl) await initController();
else if (ctrl.then) await ctrl;
if ((ctrl ??= initController()).then) ctrl = await ctrl;

if (currentDrive) return;
currentDrive = await getDrive(name);
if ((currentDrive = getDrive(name)).then) { // preventing re-entry by assigning synchronously
currentDrive = await currentDrive;
}
ctrl.use(currentDrive);

status.state = STATES.connecting;
Expand Down Expand Up @@ -176,8 +180,8 @@ export async function syncNow() {
//#endregion
//#region Utils

async function initController() {
ctrl = await (ctrl = dbToCloud.dbToCloud({
function initController() {
return dbToCloud({
onGet: _id => styleMan.uuid2style(_id) || uuidIndex.custom[_id],
async onPut(doc) {
if (!doc) return; // TODO: delete it?
Expand Down Expand Up @@ -230,7 +234,7 @@ async function initController() {
retryMaxAttempts: 10,
retryExp: 1.2,
retryDelay: 6,
}));
});
}

function emitStatusChange() {
Expand Down Expand Up @@ -269,22 +273,19 @@ function getErrorBadge() {
}

async function getDrive(name) {
if (name === 'dropbox' || name === 'google' || name === 'onedrive' || name === 'webdav') {
const options = await getDriveOptions(name);
options.getAccessToken = () => getToken(name);
options.fetch = name === 'webdav' ? fetchWebDAV.bind(options) : fetch;
return dbToCloud.drive[name].default(options);
if (!hasOwn(cloudDrive, name)) throw new Error(`Unknown cloud provider: ${name}`);
const options = await getDriveOptions(name);
const webdav = name === 'webdav';
const getAccessToken = () => getToken(name);
if (!process.env.MV3) {
options[kGetAccessToken] = getAccessToken;
options.fetch = webdav ? fetchWebDAV.bind(options) : fetch;
} else if (webdav) {
API.sync[kGetAccessToken] = getAccessToken;
} else {
options[kGetAccessToken] = getAccessToken;
}
throw new Error(`unknown cloud name: ${name}`);
}

/** @this {Object} DriveOptions */
function fetchWebDAV(url, init = {}) {
init.credentials = 'omit'; // circumventing nextcloud CSRF token error
init.headers = Object.assign({}, init.headers, {
Authorization: `Basic ${btoa(`${this.username || ''}:${this.password || ''}`)}`,
});
return fetch(url, init);
return cloudDrive[name](options);
}

function schedule(delay = SYNC_DELAY) {
Expand All @@ -299,7 +300,7 @@ function translateErrorMessage(err) {
return browser.i18n.getMessage('syncErrorLock',
new Date(err.expire).toLocaleString([], {timeStyle: 'short'}));
}
return err.message || String(err);
return err.message || JSON.stringify(err);
}

//#endregion
108 changes: 57 additions & 51 deletions src/background/token-manager.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import launchWebAuthFlow from 'webext-launch-web-auth-flow';
import {browserWindows, clamp, FIREFOX, URLS} from '/js/toolbox';
import {kAppUrlencoded, kContentType} from '/js/consts';
import {DNR_ID_IDENTITY, updateDNR} from '/js/dnr';
import {chromeLocal} from '/js/storage-util';
import {browserWindows, clamp, FIREFOX, URLS} from '/js/toolbox';
import {isVivaldi} from './common';
import {waitForTabUrl} from './tab-util';
import launchWebAuthFlow from 'webext-launch-web-auth-flow';

const AUTH = {
dropbox: {
Expand Down Expand Up @@ -139,10 +140,11 @@ async function refreshToken(name, k, obj) {
async function authUser(keys, name, interactive = false, hooks = null) {
const provider = AUTH[name];
const state = Math.random().toFixed(8).slice(2);
const customRedirectUri = provider.redirect_uri;
const query = {
response_type: provider.flow,
client_id: provider.clientId,
redirect_uri: provider.redirect_uri || DEFAULT_REDIRECT_URI,
redirect_uri: customRedirectUri || DEFAULT_REDIRECT_URI,
state,
};
if (provider.scopes) {
Expand All @@ -151,29 +153,10 @@ async function authUser(keys, name, interactive = false, hooks = null) {
if (provider.authQuery) {
Object.assign(query, provider.authQuery);
}
if (alwaysUseTab == null) {
alwaysUseTab = await detectVivaldiWebRequestBug();
}
if (hooks) hooks.query(query);
hooks?.query(query);
const url = `${provider.authURL}?${new URLSearchParams(query)}`;
const width = clamp(screen.availWidth - 100, 400, 800);
const height = clamp(screen.availHeight - 100, 200, 800);
const wnd = !alwaysUseTab && await browserWindows.getLastFocused();
const finalUrl = await launchWebAuthFlow({
url,
alwaysUseTab,
interactive,
redirect_uri: query.redirect_uri,
windowOptions: wnd && Object.assign({
state: 'normal',
width,
height,
}, wnd.state !== 'minimized' && {
// Center the popup to the current window
top: Math.ceil(wnd.top + (wnd.height - width) / 2),
left: Math.ceil(wnd.left + (wnd.width - width) / 2),
}),
});
const finalUrl = await (process.env.MV3 ? authUserMV3 : authUserMV2)(url, interactive,
customRedirectUri);
const params = new URLSearchParams(
provider.flow === 'token' ?
new URL(finalUrl).hash.slice(1) :
Expand Down Expand Up @@ -206,6 +189,53 @@ async function authUser(keys, name, interactive = false, hooks = null) {
return handleTokenResult(result, keys);
}

async function authUserMV2(url, interactive, redirectUri) {
alwaysUseTab ??= await isVivaldi;
const width = clamp(screen.availWidth - 100, 400, 800);
const height = clamp(screen.availHeight - 100, 200, 800);
const wnd = !alwaysUseTab && await browserWindows.getLastFocused();
return launchWebAuthFlow({
url,
alwaysUseTab,
interactive,
redirect_uri: redirectUri || DEFAULT_REDIRECT_URI,
windowOptions: wnd && Object.assign({
state: 'normal',
width,
height,
}, wnd.state !== 'minimized' && {
// Center the popup to the current window
top: Math.ceil(wnd.top + (wnd.height - width) / 2),
left: Math.ceil(wnd.left + (wnd.width - width) / 2),
}),
});
}

async function authUserMV3(url, interactive, redirectUri) {
if (redirectUri) {
await updateDNR([{
id: DNR_ID_IDENTITY,
condition: {
urlFilter: '|' + redirectUri,
resourceTypes: ['main_frame'],
},
action: {
type: 'redirect',
redirect: {
transform: {
host: DEFAULT_REDIRECT_URI.split('/')[2],
},
},
},
}]);
}
try {
return await chrome.identity.launchWebAuthFlow({interactive, url});
} finally {
await updateDNR(null, [DNR_ID_IDENTITY]);
}
}

async function handleTokenResult(result, k) {
await chromeLocal.set({
[k.TOKEN]: result.access_token,
Expand All @@ -220,9 +250,7 @@ async function handleTokenResult(result, k) {
async function postQuery(url, body) {
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
headers: {[kContentType]: kAppUrlencoded},
body: body ? new URLSearchParams(body) : null,
};
const r = await fetch(url, options);
Expand All @@ -234,25 +262,3 @@ async function postQuery(url, body) {
err.code = r.status;
throw err;
}

async function detectVivaldiWebRequestBug() {
// Workaround for https://github.com/openstyles/stylus/issues/1182
if (!(isVivaldi.then ? await isVivaldi : isVivaldi)) {
return false;
}
let bugged = true;
const TEST_URL = chrome.runtime.getURL('manifest.json');
const check = ({url}) => {
bugged = url !== TEST_URL;
};
chrome.webRequest.onBeforeRequest.addListener(check, {urls: [TEST_URL], types: ['main_frame']});
const {tabs: [tab]} = await browserWindows.create({
type: 'popup',
state: 'minimized',
url: TEST_URL,
});
await waitForTabUrl(tab.id);
browserWindows.remove(tab.windowId);
chrome.webRequest.onBeforeRequest.removeListener(check);
return bugged;
}
23 changes: 10 additions & 13 deletions src/background/usercss-install-helper.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import browser from '/js/browser';
import {DNR_ID_INSTALLER} from '/js/dnr';
import {kContentType} from '/js/consts';
import {DNR_ID_INSTALLER, updateDNR} from '/js/dnr';
import * as prefs from '/js/prefs';
import {FIREFOX, RX_META, URLS} from '/js/toolbox';
import {bgReady} from './common';
Expand All @@ -22,7 +23,9 @@ export function getInstallCode(url) {
}

function toggle(key, val) {
if (!process.env.MV3) chrome.webRequest.onHeadersReceived.removeListener(maybeInstallByMime);
if (!process.env.MV3) {
chrome.webRequest.onHeadersReceived.removeListener(maybeInstallByMime);
}
tabMan.onOff(maybeInstall, val);
const urls = val ? [''] : [
/* Known distribution sites where we ignore urlInstaller option, because
Expand All @@ -33,9 +36,7 @@ function toggle(key, val) {
...['greasy', 'sleazy'].map(h => `https://update.${h}fork.org/`),
];
if (process.env.MV3) {
const header = 'content-type';
/** @type {chrome.declarativeNetRequest.Rule[]} */
const rules = [{
updateDNR([{
id: DNR_ID_INSTALLER,
condition: {
regexFilter: val
Expand All @@ -45,20 +46,16 @@ function toggle(key, val) {
? undefined
: [...new Set(urls.map(u => u.split('/')[2]))],
resourceTypes: ['main_frame'],
responseHeaders: [{header, values: ['text/*']}],
excludedResponseHeaders: [{header, values: ['text/html']}],
responseHeaders: [{header: kContentType, values: ['text/*']}],
excludedResponseHeaders: [{header: kContentType, values: ['text/html']}],
},
action: {
type: 'redirect',
redirect: {
regexSubstitution: chrome.runtime.getURL(URLS.installUsercss + '#\\0'),
},
},
}];
chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: rules.map(r => r.id),
addRules: rules,
});
}]);
} else {
chrome.webRequest.onHeadersReceived.addListener(maybeInstallByMime, {
urls: urls.reduce(reduceUsercssGlobs, []),
Expand Down Expand Up @@ -116,7 +113,7 @@ async function maybeInstall({tabId, url, oldUrl = ''}) {
}

function maybeInstallByMime({tabId, url, responseHeaders}) {
const h = responseHeaders.find(_ => _.name.toLowerCase() === 'content-type');
const h = responseHeaders.find(_ => _.name.toLowerCase() === kContentType);
const isText = h && isContentTypeText(h.value);
tabMan.set(tabId, isContentTypeText.name, isText);
if (isText) {
Expand Down
Loading

0 comments on commit 9e4eb44

Please sign in to comment.