From 8538e403e4db91b46e411d364bd05fc88fa4eb01 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 30 May 2023 15:03:34 +0200 Subject: [PATCH 01/46] First pass migrating to manifest v3 --- html/background.html | 11 --- lib/chrome-promise.js | 83 --------------------- manifest.json | 19 +++-- package-lock.json | 27 +++---- package.json | 14 +++- src/entries/background-script.js | 8 +- src/entries/options.js | 1 - src/lib/Account.ts | 7 +- src/lib/Controller.ts | 106 ++++++++++++++++++++++++--- src/lib/Logger.js | 8 +- src/lib/adapters/GoogleDrive.ts | 6 +- src/lib/adapters/WebDav.ts | 8 +- src/lib/browser-api.js | 61 ++++++++++++++- src/lib/browser/BrowserAccount.ts | 16 ++-- src/lib/browser/BrowserController.js | 34 +++++---- src/lib/browser/BrowserTree.ts | 6 +- src/lib/interfaces/Controller.ts | 8 +- src/lib/native/NativeAccount.ts | 6 +- src/lib/native/NativeController.js | 34 +++++---- src/lib/serializers/Xbel.ts | 2 +- src/lib/strategies/Default.ts | 2 +- src/ui/plugins/capacitor.js | 9 +-- src/ui/store/actions.js | 6 +- 23 files changed, 275 insertions(+), 207 deletions(-) delete mode 100644 html/background.html delete mode 100644 lib/chrome-promise.js diff --git a/html/background.html b/html/background.html deleted file mode 100644 index 0103f05640..0000000000 --- a/html/background.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Floccus - - - - - - diff --git a/lib/chrome-promise.js b/lib/chrome-promise.js deleted file mode 100644 index ab445ccc85..0000000000 --- a/lib/chrome-promise.js +++ /dev/null @@ -1,83 +0,0 @@ -/*! - * chrome-promise 1.0.7 - * https://github.com/tfoxy/chrome-promise - * - * Copyright 2015 Tomás Fox - * Released under the MIT license - */ - -(function(root, factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define([], factory.bind(null, typeof exports === 'object' ? this : root)); - } else if (typeof exports === 'object') { - // Node. Does not work with strict CommonJS, but - // only CommonJS-like environments that support module.exports, - // like Node. - module.exports = factory(this); - } else { - // Browser globals (root is window) - root.ChromePromise = factory(root); - } -}(this, function(root) { - 'use strict'; - var push = Array.prototype.push, - hasOwnProperty = Object.prototype.hasOwnProperty; - - return ChromePromise; - - //////////////// - - function ChromePromise(chrome, Promise) { - chrome = chrome || root.chrome; - Promise = Promise || root.Promise; - - var runtime = chrome.runtime; - - fillProperties(chrome, this); - - //////////////// - - function setPromiseFunction(fn, thisArg) { - - return function() { - var args = arguments; - - return new Promise(function(resolve, reject) { - function callback() { - var err = runtime.lastError; - if (err) { - reject(err); - } else { - resolve.apply(null, arguments); - } - } - - push.call(args, callback); - - fn.apply(thisArg, args); - }); - - }; - - } - - function fillProperties(source, target) { - for (var key in source) { - if (hasOwnProperty.call(source, key)) { - var val = source[key]; - var type = typeof val; - - if (type === 'object' && !(val instanceof ChromePromise) && key.indexOf('on') != 0) { - target[key] = {}; - fillProperties(val, target[key]); - } else if (type === 'function') { - target[key] = setPromiseFunction(val, source); - } else { - target[key] = val; - } - } - } - } - } -})); diff --git a/manifest.json b/manifest.json index bcaecc165d..3bf67dfafc 100644 --- a/manifest.json +++ b/manifest.json @@ -1,5 +1,5 @@ { - "manifest_version": 2, + "manifest_version": 3, "name": "floccus bookmarks sync", "short_name": "floccus", "version": "4.19.1", @@ -19,18 +19,21 @@ "default_locale": "en", - "permissions": ["https://*/", "http://*/", "alarms", "bookmarks", "storage", "unlimitedStorage", "tabs", "identity"], - "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self';", + "permissions": ["alarms", "bookmarks", "storage", "unlimitedStorage", "tabs", "identity"], + "host_permissions": [ + "*://*/*" + ], + "content_security_policy": { + "extension_pages": "script-src 'self'; object-src 'self';" + }, "options_ui": { "page": "dist/html/options.html", - "browser_style": false, - "chrome_style": false + "browser_style": false }, - "browser_action": { + "action": { "browser_style": false, - "chrome_style": false, "default_icon": { "48": "icons/logo.png" }, @@ -39,6 +42,6 @@ }, "background": { - "page": "dist/html/background.html" + "service_worker": "dist/js/background-script.js" } } diff --git a/package-lock.json b/package-lock.json index c8227bca41..cfe8e5ac62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,6 @@ "intl-messageformat": "^9.9.1", "js-base64": "^3.7.5", "lodash": "^4.17.20", - "onwakeup": "^0.0.2", "p-queue": "^5.0.0", "punycode": "^2.1.1", "random": "^2.2.0", @@ -4498,9 +4497,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001468", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001468.tgz", - "integrity": "sha512-zgAo8D5kbOyUcRAgSmgyuvBkjrGk5CGYG5TYgFdpQv+ywcyEpo1LOWoG8YmoflGnh+V+UsNuKYedsoYs0hzV5A==", + "version": "1.0.30001491", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001491.tgz", + "integrity": "sha512-17EYIi4TLnPiTzVKMveIxU5ETlxbSO3B6iPvMbprqnKh4qJsQGk5Nh1Lp4jIMAE0XfrujsJuWZAM3oJdMHaKBA==", "dev": true, "funding": [ { @@ -4510,6 +4509,10 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ] }, @@ -11269,11 +11272,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/onwakeup": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/onwakeup/-/onwakeup-0.0.2.tgz", - "integrity": "sha512-CNRpi/d+0Q9Ep+gn5L8eRLhKj9itLWnPk3cmYMz9SYlgUSB7HffRr0VkYuvPpCQ0tgFLVLFqlhbLvNao+Yqb4g==" - }, "node_modules/open": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", @@ -19662,9 +19660,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001468", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001468.tgz", - "integrity": "sha512-zgAo8D5kbOyUcRAgSmgyuvBkjrGk5CGYG5TYgFdpQv+ywcyEpo1LOWoG8YmoflGnh+V+UsNuKYedsoYs0hzV5A==", + "version": "1.0.30001491", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001491.tgz", + "integrity": "sha512-17EYIi4TLnPiTzVKMveIxU5ETlxbSO3B6iPvMbprqnKh4qJsQGk5Nh1Lp4jIMAE0XfrujsJuWZAM3oJdMHaKBA==", "dev": true }, "caseless": { @@ -24885,11 +24883,6 @@ "mimic-fn": "^4.0.0" } }, - "onwakeup": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/onwakeup/-/onwakeup-0.0.2.tgz", - "integrity": "sha512-CNRpi/d+0Q9Ep+gn5L8eRLhKj9itLWnPk3cmYMz9SYlgUSB7HffRr0VkYuvPpCQ0tgFLVLFqlhbLvNao+Yqb4g==" - }, "open": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", diff --git a/package.json b/package.json index fa7232a5ae..392f0327bd 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,6 @@ "intl-messageformat": "^9.9.1", "js-base64": "^3.7.5", "lodash": "^4.17.20", - "onwakeup": "^0.0.2", "p-queue": "^5.0.0", "punycode": "^2.1.1", "random": "^2.2.0", @@ -111,6 +110,17 @@ "> 0.25%", "last 2 versions and supports es6-generators", "Firefox ESR", - "not dead" + "not dead", + "not bb >= 0", + "not and_chr >= 0", + "not and_ff >= 0", + "not and_qq >= 0", + "not and_uc >= 0", + "not ie_mob >= 0", + "not op_mini all", + "not op_mob >= 0", + "not Safari >= 0", + "not KaiOS >= 0", + "not Samsung >= 0" ] } diff --git a/src/entries/background-script.js b/src/entries/background-script.js index 44ee8b19be..0404bb0583 100644 --- a/src/entries/background-script.js +++ b/src/entries/background-script.js @@ -1,8 +1,4 @@ import BrowserController from '../lib/browser/BrowserController' -window.controller = new BrowserController() - -const onload = () => { - window.controller.onLoad() -} -window.addEventListener('load', onload) +const controller = new BrowserController() +controller.onLoad() diff --git a/src/entries/options.js b/src/entries/options.js index dbc22880e1..f2305926c9 100644 --- a/src/entries/options.js +++ b/src/entries/options.js @@ -1,3 +1,2 @@ import app from '../ui' - app() diff --git a/src/lib/Account.ts b/src/lib/Account.ts index 53ed2b5119..7aa33c3a10 100644 --- a/src/lib/Account.ts +++ b/src/lib/Account.ts @@ -8,7 +8,7 @@ import IAccountStorage, { IAccountData, TAccountStrategy } from './interfaces/Ac import { TAdapter } from './interfaces/Adapter' import { IResource, TLocalTree } from './interfaces/Resource' import Controller from './Controller' -import { Device } from '@capacitor/device' +import { Capacitor } from '@capacitor/core' import IAccount from './interfaces/Account' import Mappings from './Mappings' @@ -24,7 +24,7 @@ export default class Account { static singleton : IAccount static async getAccountClass(): Promise { - if ((await Device.getInfo()).platform === 'web') { + if (Capacitor.getPlatform() === 'web') { this.singleton = (await import('./browser/BrowserAccount')).default } else { this.singleton = (await import('./native/NativeAccount')).default @@ -109,7 +109,8 @@ export default class Account { async setData(data:IAccountData):Promise { const controller = await Controller.getSingleton() - await this.storage.setAccountData(data, controller.key) + const key = await controller.getKey() + await this.storage.setAccountData(data, key) this.server.setData(data) } diff --git a/src/lib/Controller.ts b/src/lib/Controller.ts index b1681b65c0..d0b2fac64c 100644 --- a/src/lib/Controller.ts +++ b/src/lib/Controller.ts @@ -1,20 +1,106 @@ -import { Device } from '@capacitor/device' import IController from './interfaces/Controller' -export default class Controller { +export default class Controller implements IController { static singleton: IController static async getSingleton():Promise { if (!this.singleton) { - if ((await Device.getInfo()).platform === 'web') { - const browser = (await import('./browser-api')).default - const background = await browser.runtime.getBackgroundPage() - this.singleton = background.controller - } else { - const NativeController = await import('./native/NativeController') - this.singleton = NativeController.default.getSingleton() - } + this.singleton = new Controller } return this.singleton } + + cancelSync(accountId, keepEnabled): Promise { + return navigator.serviceWorker.ready.then((registration) => { + const worker = registration.active + worker.postMessage({type: 'cancelSync', params: [accountId, keepEnabled]}) + }) + } + + onStatusChange(listener): () => void { + const eventListener = (event) => { + const {type} = event.data + if (type === 'onStatusChange') { + listener() + } + } + navigator.serviceWorker.addEventListener('message', eventListener) + return function() { + navigator.serviceWorker.removeEventListener('message', eventListener) + } + } + + scheduleSync(accountId, wait): Promise { + return navigator.serviceWorker.ready.then((registration) => { + const worker = registration.active + worker.postMessage({type: 'scheduleSync', params: [accountId, wait]}) + }) + } + + setEnabled(enabled: boolean): void { + navigator.serviceWorker.ready.then((registration) => { + const worker = registration.active + worker.postMessage({type: 'setEnabled', params: [enabled]}) + }) + } + + setKey(key): Promise { + return navigator.serviceWorker.ready.then((registration) => { + const worker = registration.active + worker.postMessage({type: 'setKey', params: [key]}) + }) + } + + syncAccount(accountId, strategy): Promise { + return navigator.serviceWorker.ready.then((registration) => { + const worker = registration.active + worker.postMessage({type: 'setEnabled', params: [accountId, strategy]}) + }) + } + + unlock(key): Promise { + return navigator.serviceWorker.ready.then((registration) => { + const worker = registration.active + worker.postMessage({type: 'unlock', params: [key]}) + }) + } + + unsetKey(): Promise { + return navigator.serviceWorker.ready.then((registration) => { + const worker = registration.active + worker.postMessage({type: 'unsetKey', params: []}) + }) + } + + getKey(): Promise { + return new Promise((resolve) => { + const eventListener = (event) => { + if (event.data.type === 'getKeyResponse') { + resolve(event.data.params[0]) + navigator.serviceWorker.removeEventListener('message', eventListener) + } + } + navigator.serviceWorker.addEventListener('message', eventListener) + navigator.serviceWorker.ready.then((registration) => { + const worker = registration.active + worker.postMessage({ type: 'getKey', params: [] }) + }) + }) + } + + getUnlocked(): Promise { + return new Promise((resolve) => { + const eventListener = (event) => { + if (event.data.type === 'getUnlockedResponse') { + resolve(event.data.params[0]) + navigator.serviceWorker.removeEventListener('message', eventListener) + } + } + navigator.serviceWorker.addEventListener('message', eventListener) + navigator.serviceWorker.ready.then((registration) => { + const worker = registration.active + worker.postMessage({ type: 'getUnlocked', params: [] }) + }) + }) + } } diff --git a/src/lib/Logger.js b/src/lib/Logger.js index 37617303ee..8fa7d80033 100644 --- a/src/lib/Logger.js +++ b/src/lib/Logger.js @@ -1,11 +1,11 @@ /* global DEBUG */ -import { Device } from '@capacitor/device' import util from 'util' import * as Parallel from 'async-parallel' import packageJson from '../../package.json' import Crypto from './Crypto' import { Share } from '@capacitor/share' import { Filesystem, Directory, Encoding } from '@capacitor/filesystem' +import { Capacitor } from '@capacitor/core' export default class Logger { static log() { @@ -17,7 +17,7 @@ export default class Logger { } static async persist() { - const Storage = ((await Device.getInfo()).platform === 'web') ? await import('./browser/BrowserAccountStorage') : await import('./native/NativeAccountStorage') + const Storage = (Capacitor.getPlatform() === 'web') ? await import('./browser/BrowserAccountStorage') : await import('./native/NativeAccountStorage') await Storage.default.changeEntry( 'logs', log => { @@ -30,7 +30,7 @@ export default class Logger { } static async getLogs() { - const Storage = ((await Device.getInfo()).platform === 'web') ? await import('./browser/BrowserAccountStorage') : await import('./native/NativeAccountStorage') + const Storage = (Capacitor.getPlatform() === 'web') ? await import('./browser/BrowserAccountStorage') : await import('./native/NativeAccountStorage') return Storage.default.getEntry('logs', []) } @@ -88,7 +88,7 @@ export default class Logger { } static async download(filename, blob) { - if ((await Device.getInfo()).platform === 'web') { + if (Capacitor.getPlatform() === 'web') { const element = document.createElement('a') let objectUrl = URL.createObjectURL(blob) diff --git a/src/lib/adapters/GoogleDrive.ts b/src/lib/adapters/GoogleDrive.ts index 3cc07e1c96..92243d378d 100644 --- a/src/lib/adapters/GoogleDrive.ts +++ b/src/lib/adapters/GoogleDrive.ts @@ -11,7 +11,7 @@ import { OAuthTokenError } from '../../errors/Error' import { OAuth2Client } from '@byteowls/capacitor-oauth2' -import { Device } from '@capacitor/device' +import { Capacitor } from '@capacitor/core' import { Http } from '@capacitor-community/http' const OAuthConfig = { @@ -58,7 +58,7 @@ export default class GoogleDriveAdapter extends CachingAdapter { } static async authorize(interactive = true) { - const { platform } = await Device.getInfo() + const platform = Capacitor.getPlatform() if (platform !== 'web') { const result = await OAuth2Client.authenticate(OAuthConfig) @@ -134,7 +134,7 @@ export default class GoogleDriveAdapter extends CachingAdapter { } async getAccessToken(refreshToken:string) { - const {platform} = await Device.getInfo() + const platform = Capacitor.getPlatform() const credentialType = platform const response = await this.request('POST', 'https://oauth2.googleapis.com/token', diff --git a/src/lib/adapters/WebDav.ts b/src/lib/adapters/WebDav.ts index 7420e8417f..b593fadcc3 100644 --- a/src/lib/adapters/WebDav.ts +++ b/src/lib/adapters/WebDav.ts @@ -14,7 +14,7 @@ import { SlashError } from '../../errors/Error' import { Http } from '@capacitor-community/http' -import { Device } from '@capacitor/device' +import { Capacitor } from '@capacitor/core' import Html from '../serializers/Html' const LOCK_INTERVAL = 2 * 60 * 1000 // Lock every 2mins while syncing @@ -283,8 +283,7 @@ export default class WebDavAdapter extends CachingAdapter { } async uploadFile(url, content_type, data) { - const info = await Device.getInfo() - if (info.platform === 'web') { + if (Capacitor.getPlatform() === 'web') { return this.uploadFileWeb(url, content_type, data) } else { return this.uploadFileNative(url, content_type, data) @@ -352,8 +351,7 @@ export default class WebDavAdapter extends CachingAdapter { } async downloadFile(url) { - const info = await Device.getInfo() - if (info.platform === 'web') { + if (Capacitor.getPlatform() === 'web') { return this.downloadFileWeb(url) } else { return this.downloadFileNative(url) diff --git a/src/lib/browser-api.js b/src/lib/browser-api.js index daac84cb04..807d4c812e 100644 --- a/src/lib/browser-api.js +++ b/src/lib/browser-api.js @@ -1,4 +1,63 @@ -/* global ChromePromise chrome browser */ +/* global chrome browser */ + +const ChromePromise = (function(root) { + 'use strict' + var push = Array.prototype.push, + hasOwnProperty = Object.prototype.hasOwnProperty + + return ChromePromise + + function ChromePromise(chrome, Promise) { + chrome = chrome || root.chrome + Promise = Promise || root.Promise + + var runtime = chrome.runtime + + fillProperties(chrome, this) + + /// ///////////// + + function setPromiseFunction(fn, thisArg) { + return function() { + var args = arguments + + return new Promise(function(resolve, reject) { + function callback() { + var err = runtime.lastError + if (err) { + reject(err) + } else { + resolve.apply(null, arguments) + } + } + + push.call(args, callback) + + fn.apply(thisArg, args) + }) + } + } + + function fillProperties(source, target) { + for (var key in source) { + if (hasOwnProperty.call(source, key)) { + var val = source[key] + var type = typeof val + + if (type === 'object' && !(val instanceof ChromePromise) && key.indexOf('on') !== 0) { + target[key] = {} + fillProperties(val, target[key]) + } else if (type === 'function') { + target[key] = setPromiseFunction(val, source) + } else { + target[key] = val + } + } + } + } + } +})(this || window || self) + let b if (typeof browser === 'undefined' && typeof chrome !== 'undefined') { b = new ChromePromise() diff --git a/src/lib/browser/BrowserAccount.ts b/src/lib/browser/BrowserAccount.ts index d3e3a6ef48..efec72f9e4 100644 --- a/src/lib/browser/BrowserAccount.ts +++ b/src/lib/browser/BrowserAccount.ts @@ -13,12 +13,14 @@ import { UnknownFolderItemOrderError } from '../../errors/Error' import {i18n} from '../native/I18n' +import Controller from '../Controller' export default class BrowserAccount extends Account { static async get(id:string):Promise { const storage = new BrowserAccountStorage(id) - const background = await browser.runtime.getBackgroundPage() - const data = await storage.getAccountData(background.controller.key) + const controller = await Controller.getSingleton() + const key = await controller.getKey() + const data = await storage.getAccountData(key) const tree = new BrowserTree(storage, data.localRoot) return new BrowserAccount(id, storage, await AdapterFactory.factory(data), tree) } @@ -28,8 +30,9 @@ export default class BrowserAccount extends Account { const adapter = await AdapterFactory.factory(data) const storage = new BrowserAccountStorage(id) - const background = await browser.runtime.getBackgroundPage() - await storage.setAccountData(data, background.controller.key) + const controller = await Controller.getSingleton() + const key = await controller.getKey() + await storage.setAccountData(data, key) const tree = new BrowserTree(storage, data.localRoot) return new BrowserAccount(id, storage, adapter, tree) } @@ -68,8 +71,9 @@ export default class BrowserAccount extends Account { } async updateFromStorage():Promise { - const background = await browser.runtime.getBackgroundPage() - const data = await this.storage.getAccountData(background.controller.key) + const controller = await Controller.getSingleton() + const key = await controller.getKey() + const data = await this.storage.getAccountData(key) this.server.setData(data) this.localTree = new BrowserTree(this.storage, data.localRoot) } diff --git a/src/lib/browser/BrowserController.js b/src/lib/browser/BrowserController.js index 24b6bebb39..6e8f948ea0 100644 --- a/src/lib/browser/BrowserController.js +++ b/src/lib/browser/BrowserController.js @@ -6,7 +6,6 @@ import DefunctCryptography from '../DefunctCrypto' import packageJson from '../../../package.json' import BrowserAccountStorage from './BrowserAccountStorage' import uniqBy from 'lodash/uniqBy' -import onwakeup from 'onwakeup' import PQueue from 'p-queue' import Account from '../Account' @@ -64,9 +63,6 @@ export default class BrowserController { this.onchange(localId, details) ) - // Set up onWakeup - onwakeup(() => this.onWakeup()) - // Set up the alarms browser.alarms.create('checkSync', { periodInMinutes: 1 }) @@ -114,6 +110,19 @@ export default class BrowserController { // Set correct badge after waiting a bit setTimeout(() => this.updateStatus(), 3000) + + // Setup service worker messaging + + this.onStatusChange(async() => { + const clientList = await self.clients.matchAll() + clientList.forEach(client => client.postMessage({type: 'onStatusChange', params: []})) + }) + + addEventListener('message', async(event) => { + const {type, params} = event.data + const result = await this[type](...params) + event.source.postMessage({type: type + 'Response', params: [result]}) + }) } setEnabled(enabled) { @@ -199,6 +208,14 @@ export default class BrowserController { await Promise.all(accounts.map(a => a.setData(a.getData()))) } + getKey() { + return Promise.resolve(this.key) + } + + getUnlocked() { + return Promise.resolve(this.unlocked) + } + async onchange(localId, details) { if (!this.enabled) { return @@ -373,13 +390,4 @@ export default class BrowserController { }) ) } - - async onWakeup() { - const accounts = await Account.getAllAccounts() - await Promise.all( - accounts.map(async acc => { - await acc.cancelSync() - }) - ) - } } diff --git a/src/lib/browser/BrowserTree.ts b/src/lib/browser/BrowserTree.ts index 8148c455db..3eec7c09b9 100644 --- a/src/lib/browser/BrowserTree.ts +++ b/src/lib/browser/BrowserTree.ts @@ -90,7 +90,7 @@ export default class BrowserTree implements IResource { }) folder.isRoot = isRoot return folder - } else if (window.location.protocol === 'moz-extension:' && node.type === 'separator') { + } else if (self.location.protocol === 'moz-extension:' && node.type === 'separator') { // Translate mozilla separators to floccus separators return new Tree.Bookmark({ location: ItemLocation.LOCAL, @@ -121,7 +121,7 @@ export default class BrowserTree implements IResource { return } try { - if (window.location.protocol === 'moz-extension:' && url.parse(bookmark.url).hostname === 'separator.floccus.org') { + if (self.location.protocol === 'moz-extension:' && url.parse(bookmark.url).hostname === 'separator.floccus.org') { const node = await this.queue.add(() => browser.bookmarks.create({ parentId: bookmark.parentId, @@ -150,7 +150,7 @@ export default class BrowserTree implements IResource { return } try { - if (window.location.protocol === 'moz-extension:' && url.parse(bookmark.url).hostname === 'separator.floccus.org') { + if (self.location.protocol === 'moz-extension:' && url.parse(bookmark.url).hostname === 'separator.floccus.org') { // noop } else { await this.queue.add(() => diff --git a/src/lib/interfaces/Controller.ts b/src/lib/interfaces/Controller.ts index 9c15ba3e34..9f5f3176a6 100644 --- a/src/lib/interfaces/Controller.ts +++ b/src/lib/interfaces/Controller.ts @@ -1,14 +1,12 @@ export default interface IController { - key: string; - setEnabled(): void; + setEnabled(enabled:boolean): void; setKey(key):Promise; unlock(key):Promise; unsetKey():Promise; - onchange(localId, details):Promise; scheduleSync(accountId, wait):Promise; cancelSync(accountId, keepEnabled):Promise; syncAccount(accountId, strategy):Promise; - updateStatus():Promise; onStatusChange(listener):()=>void; - onLoad():Promise; + getKey():Promise; + getUnlocked():Promise; } diff --git a/src/lib/native/NativeAccount.ts b/src/lib/native/NativeAccount.ts index 0e6ddd6fda..d77c1ad21e 100644 --- a/src/lib/native/NativeAccount.ts +++ b/src/lib/native/NativeAccount.ts @@ -20,7 +20,8 @@ export default class NativeAccount extends Account { static async get(id:string):Promise { const storage = new NativeAccountStorage(id) const controller = await Controller.getSingleton() - const data = await storage.getAccountData(controller.key) + const key = await controller.getKey() + const data = await storage.getAccountData(key) const tree = new NativeTree(storage) await tree.load() return new NativeAccount(id, storage, await AdapterFactory.factory(data), tree) @@ -32,7 +33,8 @@ export default class NativeAccount extends Account { const storage = new NativeAccountStorage(id) const controller = await Controller.getSingleton() - await storage.setAccountData(data, controller.key) + const key = await controller.getKey() + await storage.setAccountData(data, key) const tree = new NativeTree(storage) await tree.load() return new NativeAccount(id, storage, adapter, tree) diff --git a/src/lib/native/NativeController.js b/src/lib/native/NativeController.js index a7aab32030..a34d34763c 100644 --- a/src/lib/native/NativeController.js +++ b/src/lib/native/NativeController.js @@ -5,7 +5,6 @@ import NativeAccountStorage from './NativeAccountStorage' import PQueue from 'p-queue' import Account from '../Account' -import onwakeup from 'onwakeup' const INACTIVITY_TIMEOUT = 1000 * 7 const DEFAULT_SYNC_INTERVAL = 15 @@ -54,9 +53,6 @@ export default class NativeController { this.alarms = new AlarmManager(this) - // Set up onWakeup - onwakeup(() => this.onWakeup()) - // lock accounts when locking is enabled Storage.get({key: 'accountsLocked' }).then(async({value: accountsLocked}) => { @@ -66,6 +62,19 @@ export default class NativeController { this.key = null } }) + + // Setup service worker messaging + + this.onStatusChange(async() => { + const clientList = await self.clients.matchAll() + clientList.forEach(client => client.postMessage({type: 'onStatusChange', params: []})) + }) + + addEventListener('message', async(event) => { + const {type, params} = event.data + const result = await this[type](...params) + event.source.postMessage({type: type + 'Response', params: [result]}) + }) } setEnabled(enabled) { @@ -122,6 +131,14 @@ export default class NativeController { await Promise.all(accounts.map(a => a.setData(a.getData()))) } + getKey() { + return Promise.resolve(this.key) + } + + getUnlocked() { + return Promise.resolve(this.unlocked) + } + async scheduleSync(accountId, wait) { if (wait) { if (this.schedule[accountId]) { @@ -206,15 +223,6 @@ export default class NativeController { }) ) } - - async onWakeup() { - const accounts = await Account.getAllAccounts() - await Promise.all( - accounts.map(async acc => { - await acc.cancelSync() - }) - ) - } } let singleton diff --git a/src/lib/serializers/Xbel.ts b/src/lib/serializers/Xbel.ts index 7a4e807114..0be9e6adef 100644 --- a/src/lib/serializers/Xbel.ts +++ b/src/lib/serializers/Xbel.ts @@ -7,7 +7,7 @@ class XbelSerializer implements Serializer { } deserialize(xbel) { - const xmlDoc = new window.DOMParser().parseFromString( + const xmlDoc = new DOMParser().parseFromString( xbel, 'application/xml' ) diff --git a/src/lib/strategies/Default.ts b/src/lib/strategies/Default.ts index a8820197ae..ab39dc83ac 100644 --- a/src/lib/strategies/Default.ts +++ b/src/lib/strategies/Default.ts @@ -46,7 +46,7 @@ export default class SyncProcess { this.actionsDone = 0 this.actionsPlanned = 0 this.canceled = false - this.isFirefox = window.location.protocol === 'moz-extension:' + this.isFirefox = self.location.protocol === 'moz-extension:' } async cancel() :Promise { diff --git a/src/ui/plugins/capacitor.js b/src/ui/plugins/capacitor.js index c0b16879ab..9724a24e26 100644 --- a/src/ui/plugins/capacitor.js +++ b/src/ui/plugins/capacitor.js @@ -1,17 +1,12 @@ -import { Device } from '@capacitor/device' +import { Capacitor } from '@capacitor/core' import { App } from '@capacitor/app' -let deviceInfo = {} -Device.getInfo().then(info => { - deviceInfo.platform = info.platform -}) - let backButtonListener = null export default { computed: { isBrowser() { - return deviceInfo.platform === 'web' || !deviceInfo.platform + return Capacitor.getPlatform() === 'web' || !Capacitor.getPlatform() }, }, mounted() { diff --git a/src/ui/store/actions.js b/src/ui/store/actions.js index 966dc6a544..62f20bba68 100644 --- a/src/ui/store/actions.js +++ b/src/ui/store/actions.js @@ -10,8 +10,10 @@ import { Base64 } from 'js-base64' export const actionsDefinition = { async [actions.LOAD_LOCKED]({ commit, dispatch, state }) { const controller = await Controller.getSingleton() - commit(mutations.SET_LOCKED, !controller.unlocked) - commit(mutations.SET_SECURED, typeof controller.key === 'string' || !controller.unlocked) + const key = await controller.getKey() + const unlocked = await controller.getUnlocked() + commit(mutations.SET_LOCKED, !unlocked) + commit(mutations.SET_SECURED, typeof key === 'string' || !unlocked) }, async [actions.UNLOCK]({commit, dispatch, state}, key) { const controller = await Controller.getSingleton() From 71f41322b727547f88d29667c5eb5543e557aa98 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 30 May 2023 16:01:47 +0200 Subject: [PATCH 02/46] Manifest v3: Fix browser-api --- html/options.html | 1 - html/test.html | 1 - src/lib/browser-api.js | 9 +++++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/html/options.html b/html/options.html index f7a621890d..2be15a0fb3 100644 --- a/html/options.html +++ b/html/options.html @@ -23,7 +23,6 @@
- diff --git a/html/test.html b/html/test.html index 37e7827bf1..0345846406 100644 --- a/html/test.html +++ b/html/test.html @@ -8,7 +8,6 @@
- diff --git a/src/lib/browser-api.js b/src/lib/browser-api.js index 807d4c812e..1d1ce439a6 100644 --- a/src/lib/browser-api.js +++ b/src/lib/browser-api.js @@ -5,8 +5,6 @@ const ChromePromise = (function(root) { var push = Array.prototype.push, hasOwnProperty = Object.prototype.hasOwnProperty - return ChromePromise - function ChromePromise(chrome, Promise) { chrome = chrome || root.chrome Promise = Promise || root.Promise @@ -56,13 +54,16 @@ const ChromePromise = (function(root) { } } } -})(this || window || self) + + return ChromePromise +})(typeof window !== 'undefined' ? window : self) let b if (typeof browser === 'undefined' && typeof chrome !== 'undefined') { - b = new ChromePromise() + b = new ChromePromise(chrome, Promise) b.alarms = chrome.alarms // Don't promisify alarms -- don't make sense, yo! b.browserAction = chrome.browserAction // apparently, they provide no callbacks for these + b.action = chrome.action // apparently, they provide no callbacks for these b.i18n = chrome.i18n } else { b = browser From 8ec03ba430db3b2eaef915aa8cc6286034f712ed Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 30 May 2023 16:02:16 +0200 Subject: [PATCH 03/46] Manifest v3: Fix Controller dispatch --- src/lib/Controller.ts | 15 +++++++++++++-- src/lib/browser/BrowserController.js | 2 +- tsconfig.json | 9 ++++++++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/lib/Controller.ts b/src/lib/Controller.ts index d0b2fac64c..9a8cb1e952 100644 --- a/src/lib/Controller.ts +++ b/src/lib/Controller.ts @@ -1,11 +1,22 @@ import IController from './interfaces/Controller' +import { Capacitor } from '@capacitor/core' export default class Controller implements IController { static singleton: IController static async getSingleton():Promise { if (!this.singleton) { - this.singleton = new Controller + // eslint-disable-next-line no-undef + if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) { + if (Capacitor.getPlatform() === 'web') { + this.singleton = new (await import('./browser/BrowserController')).default + } else { + this.singleton = new (await import('./native/NativeController')).default + } + return this.singleton + } else { + this.singleton = new Controller + } } return this.singleton } @@ -54,7 +65,7 @@ export default class Controller implements IController { syncAccount(accountId, strategy): Promise { return navigator.serviceWorker.ready.then((registration) => { const worker = registration.active - worker.postMessage({type: 'setEnabled', params: [accountId, strategy]}) + worker.postMessage({type: 'syncAccount', params: [accountId, strategy]}) }) } diff --git a/src/lib/browser/BrowserController.js b/src/lib/browser/BrowserController.js index 6e8f948ea0..a88b70fc8a 100644 --- a/src/lib/browser/BrowserController.js +++ b/src/lib/browser/BrowserController.js @@ -372,7 +372,7 @@ export default class BrowserController { } if (icon[status]) { - await browser.browserAction.setIcon(icon[status]) + await browser.action.setIcon(icon[status]) } } diff --git a/tsconfig.json b/tsconfig.json index ba380126e8..ceb19092ee 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,14 @@ "noImplicitThis": true, "esModuleInterop": true, "resolveJsonModule": true, - "lib": ["DOM" ,"es2015", "es2016", "es2017", "es2019"] + "lib": [ + "DOM", + "es2015", + "es2016", + "es2017", + "es2019", + "webworker" + ] }, "exclude": [ "node_modules", From cd7792c6226124ba536ea6f3fec5c1ab0b1cd9c2 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 30 May 2023 16:02:35 +0200 Subject: [PATCH 04/46] Manifest v3: Fix webpack development config --- webpack.dev.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack.dev.js b/webpack.dev.js index ba16e8f4e0..aeb467d607 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -4,7 +4,7 @@ const webpack = require('webpack') module.exports = merge(common, { mode: 'development', - devtool: 'eval-source-map', + devtool: 'cheap-module-source-map', plugins: [ new webpack.DefinePlugin({ 'DEBUG': JSON.stringify(true) From 0569df5105ce55ac814deceb3e3aac64af39fe11 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Thu, 1 Jun 2023 17:27:37 +0200 Subject: [PATCH 05/46] fix(native): Make manifest v3 changes work on android --- src/entries/background-script.js | 6 +- src/lib/Account.ts | 3 + src/lib/Controller.ts | 149 +++++++++++++++++++------------ src/lib/interfaces/Controller.ts | 1 + src/ui/store/native/actions.js | 3 +- src/ui/views/native/Home.vue | 3 - 6 files changed, 99 insertions(+), 66 deletions(-) diff --git a/src/entries/background-script.js b/src/entries/background-script.js index 0404bb0583..c10d8a0f4e 100644 --- a/src/entries/background-script.js +++ b/src/entries/background-script.js @@ -1,4 +1,4 @@ -import BrowserController from '../lib/browser/BrowserController' +import Controller from '../lib/Controller' -const controller = new BrowserController() -controller.onLoad() +Controller.getSingleton() + .then(controller => controller.onLoad()) diff --git a/src/lib/Account.ts b/src/lib/Account.ts index 7aa33c3a10..84f1790733 100644 --- a/src/lib/Account.ts +++ b/src/lib/Account.ts @@ -24,6 +24,9 @@ export default class Account { static singleton : IAccount static async getAccountClass(): Promise { + if (this.singleton) { + return this.singleton + } if (Capacitor.getPlatform() === 'web') { this.singleton = (await import('./browser/BrowserAccount')).default } else { diff --git a/src/lib/Controller.ts b/src/lib/Controller.ts index 9a8cb1e952..acd2e64734 100644 --- a/src/lib/Controller.ts +++ b/src/lib/Controller.ts @@ -1,31 +1,45 @@ import IController from './interfaces/Controller' import { Capacitor } from '@capacitor/core' - export default class Controller implements IController { - static singleton: IController + public static singleton: IController + private worker: Worker|null static async getSingleton():Promise { if (!this.singleton) { - // eslint-disable-next-line no-undef - if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) { - if (Capacitor.getPlatform() === 'web') { + if (Capacitor.getPlatform() === 'web') { + // eslint-disable-next-line no-undef + if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) { + // If we're on the web and inside a service worker + // load the actual implementation this.singleton = new (await import('./browser/BrowserController')).default } else { - this.singleton = new (await import('./native/NativeController')).default + // otherwise load the proxy + this.singleton = new Controller } - return this.singleton } else { - this.singleton = new Controller + // If we're not on the web, laod the implementation directly + this.singleton = new (await import('./native/NativeController')).default } } return this.singleton } - cancelSync(accountId, keepEnabled): Promise { - return navigator.serviceWorker.ready.then((registration) => { - const worker = registration.active - worker.postMessage({type: 'cancelSync', params: [accountId, keepEnabled]}) - }) + constructor(worker?: Worker) { + this.worker = worker + } + + async getWorker(): Promise { + return this.worker + ? Promise.resolve(this.worker) + : navigator.serviceWorker.ready.then((registration) => registration.active) + } + + async cancelSync(accountId, keepEnabled): Promise { + console.log('Waiting for service worker readiness') + const worker = await this.getWorker() + const message = {type: 'cancelSync', params: [accountId, keepEnabled]} + worker.postMessage(message) + console.log('Sending message to service worker: ', message) } onStatusChange(listener): () => void { @@ -35,83 +49,100 @@ export default class Controller implements IController { listener() } } - navigator.serviceWorker.addEventListener('message', eventListener) + let worker + this.getWorker().then(w => { + worker = w + worker.addEventListener('message', eventListener) + }) return function() { - navigator.serviceWorker.removeEventListener('message', eventListener) + worker.removeEventListener('message', eventListener) } } - scheduleSync(accountId, wait): Promise { - return navigator.serviceWorker.ready.then((registration) => { - const worker = registration.active - worker.postMessage({type: 'scheduleSync', params: [accountId, wait]}) - }) + async scheduleSync(accountId, wait): Promise { + console.log('Waiting for service worker readiness') + const worker = await this.getWorker() + const message = {type: 'scheduleSync', params: [accountId, wait]} + worker.postMessage(message) + console.log('Sending message to service worker: ', message) } - setEnabled(enabled: boolean): void { - navigator.serviceWorker.ready.then((registration) => { - const worker = registration.active - worker.postMessage({type: 'setEnabled', params: [enabled]}) - }) + async setEnabled(enabled: boolean): Promise { + const worker = await this.getWorker() + const message = {type: 'setEnabled', params: [enabled]} + worker.postMessage(message) + console.log('Sending message to service worker: ', message) } - setKey(key): Promise { - return navigator.serviceWorker.ready.then((registration) => { - const worker = registration.active - worker.postMessage({type: 'setKey', params: [key]}) - }) + async setKey(key): Promise { + console.log('Waiting for service worker readiness') + const worker = await this.getWorker() + const message = {type: 'setKey', params: [key]} + worker.postMessage(message) + console.log('Sending message to service worker: ', message) } - syncAccount(accountId, strategy): Promise { - return navigator.serviceWorker.ready.then((registration) => { - const worker = registration.active - worker.postMessage({type: 'syncAccount', params: [accountId, strategy]}) - }) + async syncAccount(accountId, strategy): Promise { + console.log('Waiting for service worker readiness') + const worker = await this.getWorker() + const message = {type: 'syncAccount', params: [accountId, strategy]} + worker.postMessage(message) + console.log('Sending message to service worker: ', message) } - unlock(key): Promise { - return navigator.serviceWorker.ready.then((registration) => { - const worker = registration.active - worker.postMessage({type: 'unlock', params: [key]}) - }) + async unlock(key): Promise { + console.log('Waiting for service worker readiness') + const worker = await this.getWorker() + const message = {type: 'unlock', params: [key]} + worker.postMessage(message) + console.log('Sending message to service worker: ', message) } - unsetKey(): Promise { - return navigator.serviceWorker.ready.then((registration) => { - const worker = registration.active - worker.postMessage({type: 'unsetKey', params: []}) - }) + async unsetKey(): Promise { + console.log('Waiting for service worker readiness') + const worker = await this.getWorker() + const message = {type: 'unsetKey', params: []} + worker.postMessage(message) + console.log('Sending message to service worker: ', message) } - getKey(): Promise { + async getKey(): Promise { + console.log('Waiting for service worker readiness') + const worker = await this.getWorker() + // eslint-disable-next-line no-async-promise-executor return new Promise((resolve) => { const eventListener = (event) => { if (event.data.type === 'getKeyResponse') { resolve(event.data.params[0]) - navigator.serviceWorker.removeEventListener('message', eventListener) + worker.removeEventListener('message', eventListener) } } - navigator.serviceWorker.addEventListener('message', eventListener) - navigator.serviceWorker.ready.then((registration) => { - const worker = registration.active - worker.postMessage({ type: 'getKey', params: [] }) - }) + worker.addEventListener('message', eventListener) + const message = { type: 'getKey', params: [] } + worker.postMessage(message) + console.log('Sending message to service worker: ', message) }) } - getUnlocked(): Promise { + async getUnlocked(): Promise { + console.log('Waiting for service worker readiness') + const worker = await this.getWorker() + return new Promise((resolve) => { const eventListener = (event) => { if (event.data.type === 'getUnlockedResponse') { resolve(event.data.params[0]) - navigator.serviceWorker.removeEventListener('message', eventListener) + worker.removeEventListener('message', eventListener) } } - navigator.serviceWorker.addEventListener('message', eventListener) - navigator.serviceWorker.ready.then((registration) => { - const worker = registration.active - worker.postMessage({ type: 'getUnlocked', params: [] }) - }) + worker.addEventListener('message', eventListener) + const message = { type: 'getUnlocked', params: [] } + worker.postMessage(message) + console.log('Sending message to service worker: ', message) }) } + + onLoad() { + // noop + } } diff --git a/src/lib/interfaces/Controller.ts b/src/lib/interfaces/Controller.ts index 9f5f3176a6..fd03e0d5b2 100644 --- a/src/lib/interfaces/Controller.ts +++ b/src/lib/interfaces/Controller.ts @@ -9,4 +9,5 @@ export default interface IController { onStatusChange(listener):()=>void; getKey():Promise; getUnlocked():Promise; + onLoad():void; } diff --git a/src/ui/store/native/actions.js b/src/ui/store/native/actions.js index 593047b65b..b95ca97e4a 100644 --- a/src/ui/store/native/actions.js +++ b/src/ui/store/native/actions.js @@ -108,7 +108,8 @@ export const actionsDefinition = { await commit(mutations.LOAD_TREE, await tree.getBookmarksTree(true)) }, async [actions.CREATE_ACCOUNT]({commit, dispatch, state}, data) { - const account = await Account.create({...(await AdapterFactory.getDefaultValues(data.type)), ...data}) + const defaultData = await AdapterFactory.getDefaultValues(data.type) + const account = await Account.create({...defaultData, ...data}) await dispatch(actions.LOAD_ACCOUNTS) return account.id }, diff --git a/src/ui/views/native/Home.vue b/src/ui/views/native/Home.vue index 330c817c42..145106c2b8 100644 --- a/src/ui/views/native/Home.vue +++ b/src/ui/views/native/Home.vue @@ -10,7 +10,6 @@ import { actions } from '../../store/definitions' import { routes } from '../../NativeRouter' import { SplashScreen } from '@capacitor/splash-screen' import { SendIntent } from 'send-intent' -import Controller from '../../../lib/Controller' import packageJson from '../../../../package.json' import { Preferences as Storage } from '@capacitor/preferences' import { Http } from '@capacitor-community/http' @@ -19,8 +18,6 @@ import Logger from '../../../lib/Logger' export default { name: 'Home', async created() { - const controller = await Controller.getSingleton() - await controller.onLoad() SplashScreen.hide() await this.$store.dispatch(actions.LOAD_ACCOUNTS) From b37e9905ba46838dbe99f3770356ef696ea17c22 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Thu, 1 Jun 2023 18:12:56 +0200 Subject: [PATCH 06/46] fix(native): Make manifest v3 changes work on web again --- src/lib/Controller.ts | 21 +++++++++++++++++---- src/lib/browser/BrowserController.js | 2 ++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/lib/Controller.ts b/src/lib/Controller.ts index acd2e64734..0f834f22df 100644 --- a/src/lib/Controller.ts +++ b/src/lib/Controller.ts @@ -1,8 +1,15 @@ import IController from './interfaces/Controller' import { Capacitor } from '@capacitor/core' + +interface FloccusWorker { + postMessage(data: any): void + addEventListener(event: string, fn: (event: MessageEvent) => void): void + removeEventListener(event: string, fn: (event: MessageEvent) => void): void +} + export default class Controller implements IController { public static singleton: IController - private worker: Worker|null + private worker: FloccusWorker|null static async getSingleton():Promise { if (!this.singleton) { @@ -24,14 +31,18 @@ export default class Controller implements IController { return this.singleton } - constructor(worker?: Worker) { + constructor(worker?: FloccusWorker) { this.worker = worker } - async getWorker(): Promise { + async getWorker(): Promise { return this.worker ? Promise.resolve(this.worker) - : navigator.serviceWorker.ready.then((registration) => registration.active) + : navigator.serviceWorker.ready.then((registration) => ({ + postMessage: (...args) => registration.active.postMessage(...args), + addEventListener: (...args) => navigator.serviceWorker.addEventListener(...args), + removeEventListener: (...args) => navigator.serviceWorker.removeEventListener(...args) + })) } async cancelSync(accountId, keepEnabled): Promise { @@ -113,6 +124,7 @@ export default class Controller implements IController { return new Promise((resolve) => { const eventListener = (event) => { if (event.data.type === 'getKeyResponse') { + console.log('Message response received', event.data) resolve(event.data.params[0]) worker.removeEventListener('message', eventListener) } @@ -132,6 +144,7 @@ export default class Controller implements IController { const eventListener = (event) => { if (event.data.type === 'getUnlockedResponse') { resolve(event.data.params[0]) + console.log('Message response received', event.data) worker.removeEventListener('message', eventListener) } } diff --git a/src/lib/browser/BrowserController.js b/src/lib/browser/BrowserController.js index a88b70fc8a..567f792a17 100644 --- a/src/lib/browser/BrowserController.js +++ b/src/lib/browser/BrowserController.js @@ -120,8 +120,10 @@ export default class BrowserController { addEventListener('message', async(event) => { const {type, params} = event.data + console.log('Message received', event.data) const result = await this[type](...params) event.source.postMessage({type: type + 'Response', params: [result]}) + console.log('Sending message', {type: type + 'Response', params: [result]}) }) } From 02feb21b43278ec2879fd8258be961f1c80c1155 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 3 Jun 2023 15:41:27 +0200 Subject: [PATCH 07/46] Fix manifest v3 changes to work in firefox --- src/entries/background-script.js | 6 ++- src/lib/Controller.ts | 70 +++++++++++++++------------- src/lib/browser/BrowserController.js | 28 +++++++---- src/lib/native/NativeController.js | 13 ------ 4 files changed, 60 insertions(+), 57 deletions(-) diff --git a/src/entries/background-script.js b/src/entries/background-script.js index c10d8a0f4e..74cffc0e53 100644 --- a/src/entries/background-script.js +++ b/src/entries/background-script.js @@ -1,4 +1,6 @@ import Controller from '../lib/Controller' +import BrowserController from '../lib/browser/BrowserController' -Controller.getSingleton() - .then(controller => controller.onLoad()) +const controller = new BrowserController +controller.onLoad() +Controller.singleton = controller diff --git a/src/lib/Controller.ts b/src/lib/Controller.ts index 0f834f22df..20874862a9 100644 --- a/src/lib/Controller.ts +++ b/src/lib/Controller.ts @@ -3,8 +3,8 @@ import { Capacitor } from '@capacitor/core' interface FloccusWorker { postMessage(data: any): void - addEventListener(event: string, fn: (event: MessageEvent) => void): void - removeEventListener(event: string, fn: (event: MessageEvent) => void): void + addEventListener(fn: (data: any) => void): void + removeEventListener(fn: (data: any) => void): void } export default class Controller implements IController { @@ -14,15 +14,8 @@ export default class Controller implements IController { static async getSingleton():Promise { if (!this.singleton) { if (Capacitor.getPlatform() === 'web') { - // eslint-disable-next-line no-undef - if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) { - // If we're on the web and inside a service worker - // load the actual implementation - this.singleton = new (await import('./browser/BrowserController')).default - } else { - // otherwise load the proxy - this.singleton = new Controller - } + // otherwise load the proxy + this.singleton = new Controller } else { // If we're not on the web, laod the implementation directly this.singleton = new (await import('./native/NativeController')).default @@ -36,13 +29,24 @@ export default class Controller implements IController { } async getWorker(): Promise { - return this.worker - ? Promise.resolve(this.worker) - : navigator.serviceWorker.ready.then((registration) => ({ + if (this.worker) { + return Promise.resolve(this.worker) + } + if (navigator.serviceWorker?.controller) { + return navigator.serviceWorker.ready.then((registration) => ({ postMessage: (...args) => registration.active.postMessage(...args), - addEventListener: (...args) => navigator.serviceWorker.addEventListener(...args), - removeEventListener: (...args) => navigator.serviceWorker.removeEventListener(...args) + addEventListener: (fn) => navigator.serviceWorker.addEventListener('message', (event) => fn(event.data)), + removeEventListener: (fn) => navigator.serviceWorker.removeEventListener('message', fn) })) + } + if (Capacitor.getPlatform() === 'web') { + const browser = (await import('../lib/browser-api')).default + return { + postMessage: (data) => browser.runtime.sendMessage(data), + addEventListener: (fn) => browser.runtime.onMessage.addListener(fn), + removeEventListener: (fn) => browser.runtime.onMessage.addListener(fn) + } + } } async cancelSync(accountId, keepEnabled): Promise { @@ -54,19 +58,19 @@ export default class Controller implements IController { } onStatusChange(listener): () => void { - const eventListener = (event) => { - const {type} = event.data - if (type === 'onStatusChange') { + const eventListener = (data) => { + const {type} = data + if (type === 'status:update') { listener() } } let worker this.getWorker().then(w => { worker = w - worker.addEventListener('message', eventListener) + worker.addEventListener(eventListener) }) return function() { - worker.removeEventListener('message', eventListener) + worker.removeEventListener(eventListener) } } @@ -122,14 +126,14 @@ export default class Controller implements IController { const worker = await this.getWorker() // eslint-disable-next-line no-async-promise-executor return new Promise((resolve) => { - const eventListener = (event) => { - if (event.data.type === 'getKeyResponse') { - console.log('Message response received', event.data) - resolve(event.data.params[0]) - worker.removeEventListener('message', eventListener) + const eventListener = (data) => { + if (data.type === 'getKeyResponse') { + console.log('Message response received', data) + resolve(data.params[0]) + worker.removeEventListener(eventListener) } } - worker.addEventListener('message', eventListener) + worker.addEventListener(eventListener) const message = { type: 'getKey', params: [] } worker.postMessage(message) console.log('Sending message to service worker: ', message) @@ -141,14 +145,14 @@ export default class Controller implements IController { const worker = await this.getWorker() return new Promise((resolve) => { - const eventListener = (event) => { - if (event.data.type === 'getUnlockedResponse') { - resolve(event.data.params[0]) - console.log('Message response received', event.data) - worker.removeEventListener('message', eventListener) + const eventListener = (data) => { + if (data.type === 'getUnlockedResponse') { + resolve(data.params[0]) + console.log('Message response received', data) + worker.removeEventListener(eventListener) } } - worker.addEventListener('message', eventListener) + worker.addEventListener(eventListener) const message = { type: 'getUnlocked', params: [] } worker.postMessage(message) console.log('Sending message to service worker: ', message) diff --git a/src/lib/browser/BrowserController.js b/src/lib/browser/BrowserController.js index 567f792a17..a47f010023 100644 --- a/src/lib/browser/BrowserController.js +++ b/src/lib/browser/BrowserController.js @@ -113,18 +113,28 @@ export default class BrowserController { // Setup service worker messaging + // eslint-disable-next-line no-undef + if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) { + addEventListener('message', (event) => this._receiveEvent(event.data, (data) => event.source.postMessage(data))) + } else { + browser.runtime.onMessage.addListener((data) => void (this._receiveEvent(data, (data) => browser.runtime.sendMessage(data)))) + } this.onStatusChange(async() => { - const clientList = await self.clients.matchAll() - clientList.forEach(client => client.postMessage({type: 'onStatusChange', params: []})) + if (self?.clients) { + const clientList = await self.clients.matchAll() + clientList.forEach(client => client.postMessage({ type: 'status:update', params: [] })) + } else { + browser.runtime.sendMessage({data: {type: 'status:update', params: []}}) + } }) + } - addEventListener('message', async(event) => { - const {type, params} = event.data - console.log('Message received', event.data) - const result = await this[type](...params) - event.source.postMessage({type: type + 'Response', params: [result]}) - console.log('Sending message', {type: type + 'Response', params: [result]}) - }) + async _receiveEvent(data, sendResponse) { + const {type, params} = data + console.log('Message received', data) + const result = await this[type](...params) + sendResponse({type: type + 'Response', params: [result]}) + console.log('Sending message', {type: type + 'Response', params: [result]}) } setEnabled(enabled) { diff --git a/src/lib/native/NativeController.js b/src/lib/native/NativeController.js index a34d34763c..d68159556d 100644 --- a/src/lib/native/NativeController.js +++ b/src/lib/native/NativeController.js @@ -62,19 +62,6 @@ export default class NativeController { this.key = null } }) - - // Setup service worker messaging - - this.onStatusChange(async() => { - const clientList = await self.clients.matchAll() - clientList.forEach(client => client.postMessage({type: 'onStatusChange', params: []})) - }) - - addEventListener('message', async(event) => { - const {type, params} = event.data - const result = await this[type](...params) - event.source.postMessage({type: type + 'Response', params: [result]}) - }) } setEnabled(enabled) { From c196442dc82e53e5141bd76a138eff96cbad645a Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 3 Jun 2023 15:42:47 +0200 Subject: [PATCH 08/46] Fix BrowserController#onchange: Don't error out on deleted items --- src/lib/Account.ts | 9 +++++-- src/lib/browser/BrowserController.js | 35 +++++++++++++++++++--------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/lib/Account.ts b/src/lib/Account.ts index 84f1790733..8a04a84619 100644 --- a/src/lib/Account.ts +++ b/src/lib/Account.ts @@ -124,9 +124,14 @@ export default class Account { async tracksBookmark(localId:string):Promise { if (!(await this.isInitialized())) return false const mappings = await this.storage.getMappings() - return Object.keys(mappings.getSnapshot().LocalToServer.bookmark).some( - (id) => localId === id + const snapshot = mappings.getSnapshot() + const foundBookmark = Object.keys(snapshot.LocalToServer.bookmark).some( + (id) => String(localId) === String(id) ) + const foundFolder = Object.keys(snapshot.LocalToServer.folder).some( + (id) => String(localId) === String(id) + ) + return foundBookmark || foundFolder } async init():Promise { diff --git a/src/lib/browser/BrowserController.js b/src/lib/browser/BrowserController.js index a47f010023..37538d4af0 100644 --- a/src/lib/browser/BrowserController.js +++ b/src/lib/browser/BrowserController.js @@ -235,11 +235,13 @@ export default class BrowserController { // Debounce this function this.setEnabled(false) + console.log('Changes in browser Bookmarks detected...') + const allAccounts = await BrowserAccount.getAllAccounts() // Check which accounts contain the bookmark and which used to contain (track) it const trackingAccountsFilter = await Promise.all( - allAccounts.map(async account => { + allAccounts.map(account => { return account.tracksBookmark(localId) }) ) @@ -250,22 +252,23 @@ export default class BrowserController { // Now we check the account of the new folder - let ancestors + let containingAccounts = [] try { - ancestors = await BrowserTree.getIdPathFromLocalId(localId) + const ancestors = await BrowserTree.getIdPathFromLocalId(localId) + + containingAccounts = await BrowserAccount.getAccountsContainingLocalId( + localId, + ancestors, + allAccounts + ) } catch (e) { - this.setEnabled(true) - return + console.log('Could not detect containing account from localId ', localId) } - const containingAccounts = await BrowserAccount.getAccountsContainingLocalId( - localId, - ancestors, - allAccounts - ) accountsToSync = uniqBy( accountsToSync.concat(containingAccounts), - acc => acc.id) + acc => acc.id + ) // Filter out accounts that are not enabled .filter(account => account.getData().enabled) // Filter out accounts that are syncing, because the event may stem from the sync run @@ -280,10 +283,12 @@ export default class BrowserController { } async scheduleSync(accountId, wait) { + console.log('called scheduleSync') if (wait) { if (this.schedule[accountId]) { clearTimeout(this.schedule[accountId]) } + console.log('scheduleSync: setting a timeout in ms :', INACTIVITY_TIMEOUT) this.schedule[accountId] = setTimeout( () => this.scheduleSync(accountId), INACTIVITY_TIMEOUT @@ -291,15 +296,20 @@ export default class BrowserController { return } + console.log('getting account') let account = await Account.get(accountId) + console.log('got account') if (account.getData().syncing) { + console.log('Account is already syncing. Not syncing again.') return } if (!account.getData().enabled) { + console.log('Account is not enabled. Not syncing.') return } if (this.waiting[accountId]) { + console.log('Account is already queued to be synced') return } @@ -318,12 +328,15 @@ export default class BrowserController { } async syncAccount(accountId, strategy) { + console.log('Called syncAccount ', accountId) this.waiting[accountId] = false if (!this.enabled) { + console.log('Flocccus controller is not enabled. Not syncing.') return } let account = await Account.get(accountId) if (account.getData().syncing) { + console.log('Account is already syncing. Not triggering another sync.') return } setTimeout(() => this.updateStatus(), 500) From 652838ee5833e93e9bfe8046a828892bfac7b0f1 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 3 Jun 2023 16:37:22 +0200 Subject: [PATCH 09/46] Fix bookmarks cahgne detection --- src/entries/background-script.js | 2 -- src/lib/browser/BrowserAccount.ts | 7 +++++-- src/lib/browser/BrowserController.js | 12 +++++++++++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/entries/background-script.js b/src/entries/background-script.js index 74cffc0e53..f630e6fa5d 100644 --- a/src/entries/background-script.js +++ b/src/entries/background-script.js @@ -1,6 +1,4 @@ -import Controller from '../lib/Controller' import BrowserController from '../lib/browser/BrowserController' const controller = new BrowserController controller.onLoad() -Controller.singleton = controller diff --git a/src/lib/browser/BrowserAccount.ts b/src/lib/browser/BrowserAccount.ts index efec72f9e4..f9090a083e 100644 --- a/src/lib/browser/BrowserAccount.ts +++ b/src/lib/browser/BrowserAccount.ts @@ -133,10 +133,13 @@ export default class BrowserAccount extends Account { allAccounts = allAccounts || (await this.getAllAccounts()) const accountsInvolved = allAccounts - .filter(acc => ancestors.indexOf(acc.getData().localRoot) !== -1) + .filter(acc => ancestors.includes(acc.getData().localRoot)) + .sort((a, b) => + ancestors.indexOf(a.getData().localRoot) - ancestors.indexOf(b.getData().localRoot) + ) .reverse() const lastNesterIdx = accountsInvolved.findIndex(acc => !acc.getData().nestedSync) - return accountsInvolved.slice(0, lastNesterIdx) + return accountsInvolved.slice(0, Math.max(1, lastNesterIdx)) } } diff --git a/src/lib/browser/BrowserController.js b/src/lib/browser/BrowserController.js index 37538d4af0..693f9c9e5a 100644 --- a/src/lib/browser/BrowserController.js +++ b/src/lib/browser/BrowserController.js @@ -1,4 +1,5 @@ import browser from '../browser-api' +import Controller from '../Controller' import BrowserAccount from './BrowserAccount' import BrowserTree from './BrowserTree' import Cryptography from '../Crypto' @@ -49,6 +50,10 @@ export default class BrowserController { this.alarms = new AlarmManager(this) + this.setEnabled(true) + + Controller.singleton = this + // set up change listener browser.bookmarks.onChanged.addListener((localId, details) => this.onchange(localId, details) @@ -250,21 +255,26 @@ export default class BrowserController { // Filter out any accounts that are not tracking the bookmark .filter((account, i) => trackingAccountsFilter[i]) + console.log('onchange', {accountsToSync}) + // Now we check the account of the new folder let containingAccounts = [] try { const ancestors = await BrowserTree.getIdPathFromLocalId(localId) - + console.log('onchange:', {ancestors, allAccounts}) containingAccounts = await BrowserAccount.getAccountsContainingLocalId( localId, ancestors, allAccounts ) } catch (e) { + console.log(e) console.log('Could not detect containing account from localId ', localId) } + console.log('onchange', accountsToSync.concat(containingAccounts)) + accountsToSync = uniqBy( accountsToSync.concat(containingAccounts), acc => acc.id From e5a82f896b117965c6bf564f911cdf990b03c315 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 4 Jun 2023 13:18:00 +0200 Subject: [PATCH 10/46] fix(permissions): Ask for permissions if necessary --- src/errors/Error.ts | 8 ++++++++ src/lib/adapters/GoogleDrive.ts | 18 +++++++++++++++++- src/lib/adapters/NextcloudBookmarks.ts | 10 +++++++++- src/lib/adapters/WebDav.ts | 9 ++++++++- src/ui/store/actions.js | 3 +++ 5 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/errors/Error.ts b/src/errors/Error.ts index ae6670596d..fb682aaaea 100644 --- a/src/errors/Error.ts +++ b/src/errors/Error.ts @@ -297,3 +297,11 @@ export class CreateBookmarkError extends FloccusError { Object.setPrototypeOf(this, CreateBookmarkError.prototype) } } + +export class MissingPermissionsError extends FloccusError { + constructor() { + super(`E036: Missing permissions to access the sync server`) + this.code = 36 + Object.setPrototypeOf(this, MissingPermissionsError.prototype) + } +} diff --git a/src/lib/adapters/GoogleDrive.ts b/src/lib/adapters/GoogleDrive.ts index 92243d378d..bf882ca1b2 100644 --- a/src/lib/adapters/GoogleDrive.ts +++ b/src/lib/adapters/GoogleDrive.ts @@ -6,7 +6,7 @@ import Credentials from '../../../google-api.credentials.json' import { AuthenticationError, DecryptionError, FileUnreadableError, - GoogleDriveAuthenticationError, InterruptedSyncError, + GoogleDriveAuthenticationError, InterruptedSyncError, MissingPermissionsError, NetworkError, OAuthTokenError } from '../../errors/Error' @@ -67,6 +67,14 @@ export default class GoogleDriveAdapter extends CachingAdapter { return { refresh_token, username } } + if (platform === 'web') { + const browser = (await import('../browser-api')).default + const origins = ['https://oauth2.googleapis.com', 'https://www.googleapis.com'] + if (!(await browser.permissions.contains({ origins }))) { + throw new MissingPermissionsError() + } + } + // see https://developers.google.com/identity/protocols/oauth2/native-app const challenge = Crypto.bufferToHexstr(await Crypto.getRandomBytes(128)).substr(0, 128) const state = Crypto.bufferToHexstr(await Crypto.getRandomBytes(128)).substr(0, 64) @@ -188,6 +196,14 @@ export default class GoogleDriveAdapter extends CachingAdapter { async onSyncStart() { Logger.log('onSyncStart: begin') + if (Capacitor.getPlatform() === 'web') { + const browser = (await import('../browser-api')).default + const origins = ['https://oauth2.googleapis.com/', 'https://www.googleapis.com/'] + if (!(await browser.permissions.contains({ origins }))) { + throw new MissingPermissionsError() + } + } + this.accessToken = await this.getAccessToken(this.server.refreshToken) let file diff --git a/src/lib/adapters/NextcloudBookmarks.ts b/src/lib/adapters/NextcloudBookmarks.ts index 965af80474..90ed0ec93c 100644 --- a/src/lib/adapters/NextcloudBookmarks.ts +++ b/src/lib/adapters/NextcloudBookmarks.ts @@ -1,5 +1,6 @@ // Nextcloud ADAPTER // All owncloud specifc stuff goes in here +import { Capacitor } from '@capacitor/core' import Adapter from '../interfaces/Adapter' import HtmlSerializer from '../serializers/Html' import Logger from '../Logger' @@ -17,7 +18,7 @@ import { HttpError, InconsistentBookmarksExistenceError, InconsistentServerStateError, - InterruptedSyncError, + InterruptedSyncError, MissingPermissionsError, NetworkError, ParseResponseError, RedirectError, @@ -136,6 +137,13 @@ export default class NextcloudBookmarksAdapter implements Adapter, BulkImportRes } async onSyncStart(): Promise { + if (Capacitor.getPlatform() === 'web') { + const browser = (await import('../browser-api')).default + if (!(await browser.permissions.contains({ origins: [this.server.url + '/'] }))) { + throw new MissingPermissionsError() + } + } + this.canceled = false const startDate = Date.now() const maxTimeout = LOCK_TIMEOUT diff --git a/src/lib/adapters/WebDav.ts b/src/lib/adapters/WebDav.ts index b593fadcc3..40e16daf9d 100644 --- a/src/lib/adapters/WebDav.ts +++ b/src/lib/adapters/WebDav.ts @@ -9,7 +9,7 @@ import { AuthenticationError, DecryptionError, FileUnreadableError, HttpError, InterruptedSyncError, - LockFileError, + LockFileError, MissingPermissionsError, NetworkError, RedirectError, SlashError } from '../../errors/Error' @@ -228,6 +228,13 @@ export default class WebDavAdapter extends CachingAdapter { async onSyncStart(needLock = true) { Logger.log('onSyncStart: begin') + if (Capacitor.getPlatform() === 'web') { + const browser = (await import('../browser-api')).default + if (!(await browser.permissions.contains({ origins: [this.server.url + '/'] }))) { + throw new MissingPermissionsError() + } + } + if (this.server.bookmark_file[0] === '/') { throw new SlashError() } diff --git a/src/ui/store/actions.js b/src/ui/store/actions.js index 62f20bba68..ba9673c6df 100644 --- a/src/ui/store/actions.js +++ b/src/ui/store/actions.js @@ -56,6 +56,7 @@ export const actionsDefinition = { return account.id }, async [actions.IMPORT_ACCOUNTS]({commit, dispatch, state}, accounts) { + await browser.permissions.request({origins: ['*://*/*']}) await Account.import(accounts) await dispatch(actions.LOAD_ACCOUNTS) }, @@ -102,6 +103,7 @@ export const actionsDefinition = { await Logger.downloadLogs(anonymous) }, async [actions.TEST_WEBDAV_SERVER]({commit, dispatch, state}, {rootUrl, username, password}) { + await browser.permissions.request({origins: ['*://*/*']}) let res = await fetch(`${rootUrl}`, { method: 'PROPFIND', credentials: 'omit', @@ -119,6 +121,7 @@ export const actionsDefinition = { return true }, async [actions.TEST_NEXTCLOUD_SERVER]({commit, dispatch, state}, rootUrl) { + await browser.permissions.request({origins: ['*://*/*']}) let res = await fetch(`${rootUrl}/index.php/login/v2`, {method: 'POST', headers: {'User-Agent': 'Floccus bookmarks sync'}}) if (res.status !== 200) { throw new Error(browser.i18n.getMessage('LabelLoginFlowError')) From 585877e9f539ff0fd91427752a17460cb0b98972 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 4 Jun 2023 13:18:00 +0200 Subject: [PATCH 11/46] fix(permissions): Ask for permissions if necessary --- _locales/en/messages.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index f1b8833622..af57d4d7e0 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -104,6 +104,9 @@ "Error035": { "message": "E035: Failed to create the following bookmark on the server: {0}" }, + "Error036": { + "message": "E036: Missing permissions to access the sync server" + }, "LabelWebdavurl": { "message": "WebDAV URL" }, From 393eb940740fb36cd058f94c53d85b03b531f83e Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 4 Jun 2023 16:15:40 +0200 Subject: [PATCH 12/46] [browser] remove unlock passphrase feature it doesn't work anymore with manifest v3 --- src/lib/browser/BrowserController.js | 45 ++++++++-------------------- src/ui/App.vue | 22 -------------- 2 files changed, 12 insertions(+), 55 deletions(-) diff --git a/src/lib/browser/BrowserController.js b/src/lib/browser/BrowserController.js index 693f9c9e5a..7f181cd461 100644 --- a/src/lib/browser/BrowserController.js +++ b/src/lib/browser/BrowserController.js @@ -3,7 +3,6 @@ import Controller from '../Controller' import BrowserAccount from './BrowserAccount' import BrowserTree from './BrowserTree' import Cryptography from '../Crypto' -import DefunctCryptography from '../DefunctCrypto' import packageJson from '../../../package.json' import BrowserAccountStorage from './BrowserAccountStorage' import uniqBy from 'lodash/uniqBy' @@ -129,7 +128,7 @@ export default class BrowserController { const clientList = await self.clients.matchAll() clientList.forEach(client => client.postMessage({ type: 'status:update', params: [] })) } else { - browser.runtime.sendMessage({data: {type: 'status:update', params: []}}) + browser.runtime.sendMessage({type: 'status:update', params: []}) } }) } @@ -174,44 +173,24 @@ export default class BrowserController { async unlock(key) { let d = await browser.storage.local.get({ 'accountsLocked': null }) - let e = await browser.storage.local.get({ 'rekeyAfterUpdate': null }) if (d.accountsLocked) { - if (e.rekeyAfterUpdate) { - let hashedKey = await DefunctCryptography.sha256(key) - let decryptedHash = await DefunctCryptography.decryptAES( - key, - DefunctCryptography.iv, - d.accountsLocked - ) - - if (decryptedHash !== hashedKey) { - throw new Error('The provided key was wrong') - } - - this.unlocked = true - this.key = key - await this.unsetKey() - await this.setKey(key) + let hashedKey = await Cryptography.sha256(key) + let decryptedHash = await Cryptography.decryptAES( + key, + d.accountsLocked, + 'FLOCCUS' + ) - await browser.storage.local.set({ - rekeyAfterUpdate: null - }) - } else { - let hashedKey = await Cryptography.sha256(key) - let decryptedHash = await Cryptography.decryptAES( - key, - d.accountsLocked, - 'FLOCCUS' - ) - - if (decryptedHash !== hashedKey) { - throw new Error('The provided key was wrong') - } + if (decryptedHash !== hashedKey) { + throw new Error('The provided key was wrong') } this.key = key } this.unlocked = true this.setEnabled(true) + + // remove encryption + this.unsetKey() } async unsetKey() { diff --git a/src/ui/App.vue b/src/ui/App.vue index bb3b2ba17a..35d723250a 100644 --- a/src/ui/App.vue +++ b/src/ui/App.vue @@ -34,28 +34,6 @@ {{ t('LabelFunddevelopment') }} - - - mdi-lock-outline - {{ t('LabelSecuredcredentials') }} - - - mdi-lock-open-outline - {{ t('LabelSecurecredentials') }} - - Date: Sun, 4 Jun 2023 18:09:34 +0200 Subject: [PATCH 13/46] fix(ci): Update selenium docker images to v4 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 546f8dc485..47ca532d68 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,7 +26,7 @@ jobs: TEST_HOST: nextcloud SERVER_BRANCH: ${{ matrix.server-version }} NC_APP_VERSION: ${{ matrix.app-version }} - SELENIUM_VERSION: 3 + SELENIUM_VERSION: 4 MYSQL_PASSWORD: root strategy: From 3c790c78126860d454931ef433e508bf8e6a6c99 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 4 Jun 2023 18:12:41 +0200 Subject: [PATCH 14/46] fix(manifest): Use background.scripts --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 3bf67dfafc..7163f23f37 100644 --- a/manifest.json +++ b/manifest.json @@ -42,6 +42,6 @@ }, "background": { - "service_worker": "dist/js/background-script.js" + "scripts": ["dist/js/background-script.js"] } } From 474bd728690361d072a41c7f56d258514d8f9b82 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 19 Jul 2023 18:10:57 +0200 Subject: [PATCH 15/46] tests.yml: Use selenium 4 --- .github/workflows/tests.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 47ca532d68..b55caed8f0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -37,7 +37,6 @@ jobs: npm-version: [7.x] server-version: ['26'] app-version: ['stable'] - selenium-version: ['3'] floccus-adapter: - fake - nextcloud-bookmarks @@ -93,17 +92,17 @@ jobs: services: hub: - image: selenium/hub:3 + image: selenium/hub:4 ports: - 4444:4444 firefox: - image: selenium/node-firefox:3 + image: selenium/node-firefox:4 env: HUB_HOST: hub HUB_PORT: 4444 options: --shm-size="2g" chrome: - image: selenium/node-chrome:3 + image: selenium/node-chrome:4 env: HUB_HOST: hub HUB_PORT: 4444 From 44648dbf50a51fe5cba85a13178907bd26d04693 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 19 Jul 2023 18:17:40 +0200 Subject: [PATCH 16/46] CI: cancel-in-progress --- .github/workflows/tests.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b55caed8f0..a61b5125b6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,6 +17,10 @@ on: env: APP_NAME: bookmarks +concurrency: + group: floccus-tests-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: php: runs-on: ubuntu-latest From ed4d6f985fa465abf67b08f8f4eef9d81ef687da Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 19 Jul 2023 18:44:46 +0200 Subject: [PATCH 17/46] package-lock.json: Update selenium-webdriver --- package-lock.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index cfe8e5ac62..febb101aa6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13279,14 +13279,14 @@ "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" }, "node_modules/selenium-webdriver": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.8.1.tgz", - "integrity": "sha512-p4MtfhCQdcV6xxkS7eI0tQN6+WNReRULLCAuT4RDGkrjfObBNXMJ3WT8XdK+aXTr5nnBKuh+PxIevM0EjJgkxA==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.10.0.tgz", + "integrity": "sha512-hSQPw6jgc+ej/UEcdQPG/iBwwMeCEgZr9HByY/J8ToyXztEqXzU9aLsIyrlj1BywBcStO4JQK/zMUWWrV8+riA==", "dev": true, "dependencies": { - "jszip": "^3.10.0", + "jszip": "^3.10.1", "tmp": "^0.2.1", - "ws": ">=8.11.0" + "ws": ">=8.13.0" }, "engines": { "node": ">= 14.20.0" @@ -26363,14 +26363,14 @@ "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" }, "selenium-webdriver": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.8.1.tgz", - "integrity": "sha512-p4MtfhCQdcV6xxkS7eI0tQN6+WNReRULLCAuT4RDGkrjfObBNXMJ3WT8XdK+aXTr5nnBKuh+PxIevM0EjJgkxA==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.10.0.tgz", + "integrity": "sha512-hSQPw6jgc+ej/UEcdQPG/iBwwMeCEgZr9HByY/J8ToyXztEqXzU9aLsIyrlj1BywBcStO4JQK/zMUWWrV8+riA==", "dev": true, "requires": { - "jszip": "^3.10.0", + "jszip": "^3.10.1", "tmp": "^0.2.1", - "ws": ">=8.11.0" + "ws": ">=8.13.0" }, "dependencies": { "tmp": { From b414252b13ff66e8a40290dcd64d92097632c475 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 23 Jul 2023 15:14:59 +0200 Subject: [PATCH 18/46] selenium-runner.js: Add debug logs --- test/selenium-runner.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/selenium-runner.js b/test/selenium-runner.js index 6dda58f8c2..2fb7fcf330 100644 --- a/test/selenium-runner.js +++ b/test/selenium-runner.js @@ -1,12 +1,18 @@ const fs = require('fs') const url = require('url') const { Builder } = require('selenium-webdriver') +const { Preferences, Level, Type } = require('selenium-webdriver/lib/logging') const { Options: ChromeOptions } = require('selenium-webdriver/chrome') const { Options: FirefoxOptions } = require('selenium-webdriver/firefox') const saveStats = require('./save-stats') const fetch = require('node-fetch') const VERSION = require('../package.json').version ;(async function() { + const loggingPrefs = new Preferences() + loggingPrefs.setLevel(Type.CLIENT, Level.INFO) + loggingPrefs.setLevel(Type.DRIVER, Level.INFO) + loggingPrefs.setLevel(Type.SERVER, Level.INFO) + let driver = await new Builder() .usingServer(`http://localhost:4444/wd/hub`) .forBrowser(process.env.SELENIUM_BROWSER) @@ -26,7 +32,9 @@ const VERSION = require('../package.json').version ) : null ) + .setLoggingPrefs(loggingPrefs) .build() + console.log('Driver built for browser ' + process.env.SELENIUM_BROWSER) try { let id, testUrl switch (await (await driver.getSession()).getCapability('browserName')) { @@ -50,8 +58,7 @@ const VERSION = require('../package.json').version case 'firefox': // Scrape extension id from firefox addons page await driver.installAddon( - `./builds/floccus-build-v${VERSION}.xpi`, - true + `./builds/floccus-build-v${VERSION}.zip` ) await driver.get('about:debugging') await new Promise(resolve => setTimeout(resolve, 10000)) From 70732c31953a68dfb4094d5510d6babf15a4b60f Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 23 Jul 2023 15:33:53 +0200 Subject: [PATCH 19/46] tests.yml: Try to fix selenium docker config --- .github/workflows/tests.yml | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a61b5125b6..839c149f2f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,7 +30,6 @@ jobs: TEST_HOST: nextcloud SERVER_BRANCH: ${{ matrix.server-version }} NC_APP_VERSION: ${{ matrix.app-version }} - SELENIUM_VERSION: 4 MYSQL_PASSWORD: root strategy: @@ -96,20 +95,24 @@ jobs: services: hub: - image: selenium/hub:4 + image: selenium/hub:4.10.0-20230607 ports: + - 4442:4442 + - 4443:4443 - 4444:4444 firefox: - image: selenium/node-firefox:4 + image: selenium/node-firefox:4.10.0-20230607 env: - HUB_HOST: hub - HUB_PORT: 4444 + SE_EVENT_BUS_HOST: hub + SE_EVENT_BUS_PUBLISH_PORT: 4442 + SE_EVENT_BUS_SUBSCRIBE_PORT: 4443 options: --shm-size="2g" chrome: - image: selenium/node-chrome:4 + image: selenium/node-chrome:4.10.0-20230607 env: - HUB_HOST: hub - HUB_PORT: 4444 + SE_EVENT_BUS_HOST: hub + SE_EVENT_BUS_PUBLISH_PORT: 4442 + SE_EVENT_BUS_SUBSCRIBE_PORT: 4443 options: --shm-size="2g" nextcloud: image: nextcloud:${{ matrix.server-version }} @@ -196,6 +199,17 @@ jobs: npm ci npm run build-release --if-present + - name: Wait for Selenium + run: | + apt install -y jq + while ! curl -sSL "http://localhost:4444/wd/hub/status" 2>&1 \ + | jq -r '.value.ready' 2>&1 | grep "true" >/dev/null; do + echo 'Waiting for the Grid' + sleep 1 + done + + echo "Selenium Grid is up - executing tests" + - name: Run tests working-directory: floccus env: From 49469e1fe8b73241a0199d2998640e5f1e0de500 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 23 Jul 2023 15:37:43 +0200 Subject: [PATCH 20/46] tests.yml: Install jq with sudo --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 839c149f2f..e9e46ae488 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -201,7 +201,7 @@ jobs: - name: Wait for Selenium run: | - apt install -y jq + sudo apt install -y jq while ! curl -sSL "http://localhost:4444/wd/hub/status" 2>&1 \ | jq -r '.value.ready' 2>&1 | grep "true" >/dev/null; do echo 'Waiting for the Grid' From c8db960f298d8a0d837de2db65a1fd25c5d646e0 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 23 Jul 2023 15:54:04 +0200 Subject: [PATCH 21/46] selenium-runner.js: Go back to xpi build --- test/selenium-runner.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/selenium-runner.js b/test/selenium-runner.js index 2fb7fcf330..c06e2dd3e7 100644 --- a/test/selenium-runner.js +++ b/test/selenium-runner.js @@ -58,7 +58,8 @@ const VERSION = require('../package.json').version case 'firefox': // Scrape extension id from firefox addons page await driver.installAddon( - `./builds/floccus-build-v${VERSION}.zip` + `./builds/floccus-build-v${VERSION}.xpi`, + true ) await driver.get('about:debugging') await new Promise(resolve => setTimeout(resolve, 10000)) From fa8ff013455f8326ecc20d45dbc1830f6c3e84f3 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 23 Jul 2023 16:26:28 +0200 Subject: [PATCH 22/46] selenium-runner.js: Enable selenium logging --- test/selenium-runner.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/selenium-runner.js b/test/selenium-runner.js index c06e2dd3e7..3d97bd587f 100644 --- a/test/selenium-runner.js +++ b/test/selenium-runner.js @@ -1,12 +1,14 @@ const fs = require('fs') const url = require('url') const { Builder } = require('selenium-webdriver') -const { Preferences, Level, Type } = require('selenium-webdriver/lib/logging') +const { Preferences, Level, Type, installConsoleHandler } = require('selenium-webdriver/lib/logging') const { Options: ChromeOptions } = require('selenium-webdriver/chrome') const { Options: FirefoxOptions } = require('selenium-webdriver/firefox') const saveStats = require('./save-stats') const fetch = require('node-fetch') const VERSION = require('../package.json').version +// Enable SELENIUM logging to console +installConsoleHandler() ;(async function() { const loggingPrefs = new Preferences() loggingPrefs.setLevel(Type.CLIENT, Level.INFO) @@ -58,7 +60,7 @@ const VERSION = require('../package.json').version case 'firefox': // Scrape extension id from firefox addons page await driver.installAddon( - `./builds/floccus-build-v${VERSION}.xpi`, + `${__dirname}/../builds/floccus-build-v${VERSION}.xpi`, true ) await driver.get('about:debugging') From 3ee64534d5c3b328a5bd97883c725636089abcd5 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 23 Jul 2023 16:35:33 +0200 Subject: [PATCH 23/46] selenium-runner.js: Install unzipped extension --- test/selenium-runner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/selenium-runner.js b/test/selenium-runner.js index 3d97bd587f..b39cf7823d 100644 --- a/test/selenium-runner.js +++ b/test/selenium-runner.js @@ -60,7 +60,7 @@ installConsoleHandler() case 'firefox': // Scrape extension id from firefox addons page await driver.installAddon( - `${__dirname}/../builds/floccus-build-v${VERSION}.xpi`, + `${__dirname}/`, true ) await driver.get('about:debugging') From 3a3da15f0af36fc5086b32f6fe81fea92d5c930c Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 23 Jul 2023 16:51:18 +0200 Subject: [PATCH 24/46] selenium-runner.js: New strategy for getting WebExtensionPolicy --- test/selenium-runner.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/selenium-runner.js b/test/selenium-runner.js index b39cf7823d..84366a5e07 100644 --- a/test/selenium-runner.js +++ b/test/selenium-runner.js @@ -60,15 +60,14 @@ installConsoleHandler() case 'firefox': // Scrape extension id from firefox addons page await driver.installAddon( - `${__dirname}/`, + `${__dirname}/../builds/floccus-build-v${VERSION}.zip`, true ) await driver.get('about:debugging') await new Promise(resolve => setTimeout(resolve, 10000)) testUrl = await driver.executeScript(function() { - const extension = WebExtensionPolicy.getByID( - 'floccus@handmadeideas.org' - ) + const extension = WebExtensionPolicy.getActiveExtensions() + .find(({name}) => name === 'floccus bookmarks sync') return extension.extension.baseURL }) if (!testUrl) throw new Error('Could not install extension') From cf65fa7e5ed4a48619294985419bc7194d38b4af Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 23 Jul 2023 17:16:01 +0200 Subject: [PATCH 25/46] Controller.ts: Avoid leaking event listeners --- src/lib/Controller.ts | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/lib/Controller.ts b/src/lib/Controller.ts index 20874862a9..7b86941179 100644 --- a/src/lib/Controller.ts +++ b/src/lib/Controller.ts @@ -3,8 +3,7 @@ import { Capacitor } from '@capacitor/core' interface FloccusWorker { postMessage(data: any): void - addEventListener(fn: (data: any) => void): void - removeEventListener(fn: (data: any) => void): void + addEventListener(fn: (data: any) => void): () => void } export default class Controller implements IController { @@ -35,16 +34,21 @@ export default class Controller implements IController { if (navigator.serviceWorker?.controller) { return navigator.serviceWorker.ready.then((registration) => ({ postMessage: (...args) => registration.active.postMessage(...args), - addEventListener: (fn) => navigator.serviceWorker.addEventListener('message', (event) => fn(event.data)), - removeEventListener: (fn) => navigator.serviceWorker.removeEventListener('message', fn) + addEventListener: (fn) => { + const listener = (event) => fn(event.data) + navigator.serviceWorker.addEventListener('message', listener) + return () => navigator.serviceWorker.removeEventListener('message', listener) + }, })) } if (Capacitor.getPlatform() === 'web') { const browser = (await import('../lib/browser-api')).default return { postMessage: (data) => browser.runtime.sendMessage(data), - addEventListener: (fn) => browser.runtime.onMessage.addListener(fn), - removeEventListener: (fn) => browser.runtime.onMessage.addListener(fn) + addEventListener: (fn) => { + browser.runtime.onMessage.addListener(fn) + return () => browser.runtime.onMessage.removeListener(fn) + }, } } } @@ -64,13 +68,13 @@ export default class Controller implements IController { listener() } } - let worker + let worker, removeListener this.getWorker().then(w => { worker = w - worker.addEventListener(eventListener) + removeListener = worker.addEventListener(eventListener) }) - return function() { - worker.removeEventListener(eventListener) + return () => { + removeListener && removeListener() } } @@ -130,10 +134,10 @@ export default class Controller implements IController { if (data.type === 'getKeyResponse') { console.log('Message response received', data) resolve(data.params[0]) - worker.removeEventListener(eventListener) + removeEventListener() } } - worker.addEventListener(eventListener) + const removeEventListener = worker.addEventListener(eventListener) const message = { type: 'getKey', params: [] } worker.postMessage(message) console.log('Sending message to service worker: ', message) @@ -149,10 +153,10 @@ export default class Controller implements IController { if (data.type === 'getUnlockedResponse') { resolve(data.params[0]) console.log('Message response received', data) - worker.removeEventListener(eventListener) + removeEventListener() } } - worker.addEventListener(eventListener) + const removeEventListener = worker.addEventListener(eventListener) const message = { type: 'getUnlocked', params: [] } worker.postMessage(message) console.log('Sending message to service worker: ', message) From 7c4bb7ee63bbaf25c455137df050d2ff9909c9af Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 23 Jul 2023 18:08:07 +0200 Subject: [PATCH 26/46] selenium-runner.js: Try to enable host permissions manually --- test/selenium-runner.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/selenium-runner.js b/test/selenium-runner.js index 84366a5e07..273e42c305 100644 --- a/test/selenium-runner.js +++ b/test/selenium-runner.js @@ -1,6 +1,6 @@ const fs = require('fs') const url = require('url') -const { Builder } = require('selenium-webdriver') +const { Builder, By } = require('selenium-webdriver') const { Preferences, Level, Type, installConsoleHandler } = require('selenium-webdriver/lib/logging') const { Options: ChromeOptions } = require('selenium-webdriver/chrome') const { Options: FirefoxOptions } = require('selenium-webdriver/firefox') @@ -63,6 +63,8 @@ installConsoleHandler() `${__dirname}/../builds/floccus-build-v${VERSION}.zip`, true ) + + // Get extension URL await driver.get('about:debugging') await new Promise(resolve => setTimeout(resolve, 10000)) testUrl = await driver.executeScript(function() { @@ -71,6 +73,12 @@ installConsoleHandler() return extension.extension.baseURL }) if (!testUrl) throw new Error('Could not install extension') + + // Enable permission + await driver.get('about:addons') + await (await driver.findElement(By.name('addon-card'))).click() + await (await driver.findElement(By.id('details-deck-button-permissions'))).click() + await (await driver.findElement(By.id('permission-0'))).click() break default: throw new Error('Unknown browser') From 92083c57b799654956e25d5655d1162d8b2fdf77 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 23 Jul 2023 18:37:14 +0200 Subject: [PATCH 27/46] selenium-runner.js: Try to enable host permissions manually --- test/selenium-runner.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/selenium-runner.js b/test/selenium-runner.js index 273e42c305..82320a1deb 100644 --- a/test/selenium-runner.js +++ b/test/selenium-runner.js @@ -76,8 +76,11 @@ installConsoleHandler() // Enable permission await driver.get('about:addons') + await driver.sleep(5000) await (await driver.findElement(By.name('addon-card'))).click() + await driver.sleep(2000) await (await driver.findElement(By.id('details-deck-button-permissions'))).click() + await driver.sleep(2000) await (await driver.findElement(By.id('permission-0'))).click() break default: From cb4467769debda66ae34dbb4ab0c6aca266ce039 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 23 Jul 2023 19:34:42 +0200 Subject: [PATCH 28/46] selenium-runner.js: Try to enable host permissions manually --- test/selenium-runner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/selenium-runner.js b/test/selenium-runner.js index 82320a1deb..c85bb46196 100644 --- a/test/selenium-runner.js +++ b/test/selenium-runner.js @@ -77,7 +77,7 @@ installConsoleHandler() // Enable permission await driver.get('about:addons') await driver.sleep(5000) - await (await driver.findElement(By.name('addon-card'))).click() + await (await driver.findElement(By.css('addon-card'))).click() await driver.sleep(2000) await (await driver.findElement(By.id('details-deck-button-permissions'))).click() await driver.sleep(2000) From fa54fec859fc71b54dfd6241704b5253d6139384 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 23 Jul 2023 20:16:40 +0200 Subject: [PATCH 29/46] selenium-runner.js: Try to enable host permissions manually --- test/selenium-runner.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/selenium-runner.js b/test/selenium-runner.js index c85bb46196..24286b00bf 100644 --- a/test/selenium-runner.js +++ b/test/selenium-runner.js @@ -1,6 +1,6 @@ const fs = require('fs') const url = require('url') -const { Builder, By } = require('selenium-webdriver') +const { Builder, By, until } = require('selenium-webdriver') const { Preferences, Level, Type, installConsoleHandler } = require('selenium-webdriver/lib/logging') const { Options: ChromeOptions } = require('selenium-webdriver/chrome') const { Options: FirefoxOptions } = require('selenium-webdriver/firefox') @@ -76,12 +76,12 @@ installConsoleHandler() // Enable permission await driver.get('about:addons') - await driver.sleep(5000) - await (await driver.findElement(By.css('addon-card'))).click() - await driver.sleep(2000) - await (await driver.findElement(By.id('details-deck-button-permissions'))).click() - await driver.sleep(2000) - await (await driver.findElement(By.id('permission-0'))).click() + let card = await driver.wait(until.elementLocated(By.css('addon-card')), 10000) + await card.click() + let menu = await driver.wait(until.elementLocated(By.id('details-deck-button-permissions')), 10000) + await menu.click() + let toggle = await driver.wait(until.elementLocated(By.id('permission-0')), 10000) + await toggle.click() break default: throw new Error('Unknown browser') From 9ee670367919379cc91deb8030d6bdb59d8c60ff Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 2 Aug 2023 14:28:36 +0200 Subject: [PATCH 30/46] Update selenium --- .github/workflows/tests.yml | 7 ++++--- package-lock.json | 12 ++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e9e46ae488..8c26606c87 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -95,20 +95,21 @@ jobs: services: hub: - image: selenium/hub:4.10.0-20230607 + image: selenium/hub:4.11.0-20230801 ports: - 4442:4442 - 4443:4443 - 4444:4444 firefox: - image: selenium/node-firefox:4.10.0-20230607 + image: selenium/node-firefox:4.11.0-20230801 + env: SE_EVENT_BUS_HOST: hub SE_EVENT_BUS_PUBLISH_PORT: 4442 SE_EVENT_BUS_SUBSCRIBE_PORT: 4443 options: --shm-size="2g" chrome: - image: selenium/node-chrome:4.10.0-20230607 + image: selenium/node-chrome:4.11.0-20230801 env: SE_EVENT_BUS_HOST: hub SE_EVENT_BUS_PUBLISH_PORT: 4442 diff --git a/package-lock.json b/package-lock.json index febb101aa6..332a90a1de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13279,9 +13279,9 @@ "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" }, "node_modules/selenium-webdriver": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.10.0.tgz", - "integrity": "sha512-hSQPw6jgc+ej/UEcdQPG/iBwwMeCEgZr9HByY/J8ToyXztEqXzU9aLsIyrlj1BywBcStO4JQK/zMUWWrV8+riA==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.11.1.tgz", + "integrity": "sha512-bvrnr3UZlLScErOmn8gV6cqc+1PYDHn0575CxUR2U14fMWt7OKxSy0lAThhZq4sq4d1HqP8ebz11oiHSlAQ2WA==", "dev": true, "dependencies": { "jszip": "^3.10.1", @@ -26363,9 +26363,9 @@ "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" }, "selenium-webdriver": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.10.0.tgz", - "integrity": "sha512-hSQPw6jgc+ej/UEcdQPG/iBwwMeCEgZr9HByY/J8ToyXztEqXzU9aLsIyrlj1BywBcStO4JQK/zMUWWrV8+riA==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.11.1.tgz", + "integrity": "sha512-bvrnr3UZlLScErOmn8gV6cqc+1PYDHn0575CxUR2U14fMWt7OKxSy0lAThhZq4sq4d1HqP8ebz11oiHSlAQ2WA==", "dev": true, "requires": { "jszip": "^3.10.1", From 01b3216af481e683d7e7b55b082de7aec386ecea Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 2 Aug 2023 14:37:02 +0200 Subject: [PATCH 31/46] selenium-runner.js: New try to set permission manually --- test/selenium-runner.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/test/selenium-runner.js b/test/selenium-runner.js index 24286b00bf..6faa37f4f8 100644 --- a/test/selenium-runner.js +++ b/test/selenium-runner.js @@ -76,12 +76,19 @@ installConsoleHandler() // Enable permission await driver.get('about:addons') - let card = await driver.wait(until.elementLocated(By.css('addon-card')), 10000) - await card.click() - let menu = await driver.wait(until.elementLocated(By.id('details-deck-button-permissions')), 10000) - await menu.click() - let toggle = await driver.wait(until.elementLocated(By.id('permission-0')), 10000) - await toggle.click() + await driver.sleep(10000) + await driver.executeScript(function() { + document.querySelector('addon-card').click() + }) + await driver.sleep(10000) + await driver.executeScript(function() { + document.querySelector('#details-deck-button-permissions').click() + }) + await driver.sleep(10000) + await driver.executeScript(function() { + document.querySelector('#permission-0').click() + }) + await driver.sleep(5000) break default: throw new Error('Unknown browser') From a8221bf9fd46af4c51f2ef22a0e13eeb48992496 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 2 Aug 2023 14:57:10 +0200 Subject: [PATCH 32/46] selenium-runner.js: New try to set permission manually --- test/selenium-runner.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/selenium-runner.js b/test/selenium-runner.js index 6faa37f4f8..1d65ff22f1 100644 --- a/test/selenium-runner.js +++ b/test/selenium-runner.js @@ -66,7 +66,7 @@ installConsoleHandler() // Get extension URL await driver.get('about:debugging') - await new Promise(resolve => setTimeout(resolve, 10000)) + await driver.sleep(10000) testUrl = await driver.executeScript(function() { const extension = WebExtensionPolicy.getActiveExtensions() .find(({name}) => name === 'floccus bookmarks sync') @@ -77,6 +77,12 @@ installConsoleHandler() // Enable permission await driver.get('about:addons') await driver.sleep(10000) + console.log(await driver.getPageSource()) + await driver.executeScript(function() { + document.querySelector('button[name=extension]').click() + }) + await driver.sleep(10000) + console.log(await driver.getPageSource()) await driver.executeScript(function() { document.querySelector('addon-card').click() }) From c62e8d5e9a8a9da0ae95f4fe78720c6232052298 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 22 Aug 2023 15:30:49 +0200 Subject: [PATCH 33/46] remove passphrase feature --- _locales/en/messages.json | 12 ---- src/lib/Account.ts | 5 +- src/lib/Controller.ts | 36 +--------- src/lib/browser/BrowserAccount.ts | 13 +--- src/lib/browser/BrowserController.js | 40 ++--------- src/lib/interfaces/Controller.ts | 3 - src/lib/native/NativeAccount.ts | 9 +-- src/lib/native/NativeController.js | 35 --------- src/ui/App.vue | 3 - src/ui/NativeApp.vue | 3 - src/ui/router.js | 6 -- src/ui/store/actions.js | 10 --- src/ui/store/definitions.js | 1 - src/ui/store/index.js | 1 - src/ui/store/mutations.js | 3 - src/ui/store/native/index.js | 1 - src/ui/store/native/mutations.js | 3 - src/ui/views/SetKey.vue | 102 --------------------------- 18 files changed, 14 insertions(+), 272 deletions(-) delete mode 100644 src/ui/views/SetKey.vue diff --git a/_locales/en/messages.json b/_locales/en/messages.json index af57d4d7e0..6e80e8b09f 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -266,12 +266,6 @@ "LabelAddaccount": { "message": "Add account" }, - "LabelSecurecredentials": { - "message": "Secure your credentials" - }, - "LabelSecuredcredentials": { - "message": "Your credentials are secure" - }, "LabelOpenintab": { "message": "Open in tab" }, @@ -290,12 +284,6 @@ "LabelUntitledfolder": { "message": "Untitled folder" }, - "LabelSetkey": { - "message": "Set a passphrase for floccus" - }, - "DescriptionSetkey": { - "message": "When you set a passphrase you will have to enter this pass phrase everytime you start your browser if you want to access and synchronize your bookmarks, change your settings or do anything else with floccus." - }, "LabelSetkeybutton": { "message": "Set passphrase" }, diff --git a/src/lib/Account.ts b/src/lib/Account.ts index 8a04a84619..6b3a1977d2 100644 --- a/src/lib/Account.ts +++ b/src/lib/Account.ts @@ -7,7 +7,6 @@ import DefaultSyncProcess from './strategies/Default' import IAccountStorage, { IAccountData, TAccountStrategy } from './interfaces/AccountStorage' import { TAdapter } from './interfaces/Adapter' import { IResource, TLocalTree } from './interfaces/Resource' -import Controller from './Controller' import { Capacitor } from '@capacitor/core' import IAccount from './interfaces/Account' import Mappings from './Mappings' @@ -111,9 +110,7 @@ export default class Account { } async setData(data:IAccountData):Promise { - const controller = await Controller.getSingleton() - const key = await controller.getKey() - await this.storage.setAccountData(data, key) + await this.storage.setAccountData(data, null) this.server.setData(data) } diff --git a/src/lib/Controller.ts b/src/lib/Controller.ts index 7b86941179..d208708b49 100644 --- a/src/lib/Controller.ts +++ b/src/lib/Controller.ts @@ -9,6 +9,7 @@ interface FloccusWorker { export default class Controller implements IController { public static singleton: IController private worker: FloccusWorker|null + private key: string|null|undefined static async getSingleton():Promise { if (!this.singleton) { @@ -93,14 +94,6 @@ export default class Controller implements IController { console.log('Sending message to service worker: ', message) } - async setKey(key): Promise { - console.log('Waiting for service worker readiness') - const worker = await this.getWorker() - const message = {type: 'setKey', params: [key]} - worker.postMessage(message) - console.log('Sending message to service worker: ', message) - } - async syncAccount(accountId, strategy): Promise { console.log('Waiting for service worker readiness') const worker = await this.getWorker() @@ -117,33 +110,6 @@ export default class Controller implements IController { console.log('Sending message to service worker: ', message) } - async unsetKey(): Promise { - console.log('Waiting for service worker readiness') - const worker = await this.getWorker() - const message = {type: 'unsetKey', params: []} - worker.postMessage(message) - console.log('Sending message to service worker: ', message) - } - - async getKey(): Promise { - console.log('Waiting for service worker readiness') - const worker = await this.getWorker() - // eslint-disable-next-line no-async-promise-executor - return new Promise((resolve) => { - const eventListener = (data) => { - if (data.type === 'getKeyResponse') { - console.log('Message response received', data) - resolve(data.params[0]) - removeEventListener() - } - } - const removeEventListener = worker.addEventListener(eventListener) - const message = { type: 'getKey', params: [] } - worker.postMessage(message) - console.log('Sending message to service worker: ', message) - }) - } - async getUnlocked(): Promise { console.log('Waiting for service worker readiness') const worker = await this.getWorker() diff --git a/src/lib/browser/BrowserAccount.ts b/src/lib/browser/BrowserAccount.ts index f9090a083e..dc8cff3ea9 100644 --- a/src/lib/browser/BrowserAccount.ts +++ b/src/lib/browser/BrowserAccount.ts @@ -13,14 +13,11 @@ import { UnknownFolderItemOrderError } from '../../errors/Error' import {i18n} from '../native/I18n' -import Controller from '../Controller' export default class BrowserAccount extends Account { static async get(id:string):Promise { const storage = new BrowserAccountStorage(id) - const controller = await Controller.getSingleton() - const key = await controller.getKey() - const data = await storage.getAccountData(key) + const data = await storage.getAccountData(null) const tree = new BrowserTree(storage, data.localRoot) return new BrowserAccount(id, storage, await AdapterFactory.factory(data), tree) } @@ -30,9 +27,7 @@ export default class BrowserAccount extends Account { const adapter = await AdapterFactory.factory(data) const storage = new BrowserAccountStorage(id) - const controller = await Controller.getSingleton() - const key = await controller.getKey() - await storage.setAccountData(data, key) + await storage.setAccountData(data, null) const tree = new BrowserTree(storage, data.localRoot) return new BrowserAccount(id, storage, adapter, tree) } @@ -71,9 +66,7 @@ export default class BrowserAccount extends Account { } async updateFromStorage():Promise { - const controller = await Controller.getSingleton() - const key = await controller.getKey() - const data = await this.storage.getAccountData(key) + const data = await this.storage.getAccountData(null) this.server.setData(data) this.localTree = new BrowserTree(this.storage, data.localRoot) } diff --git a/src/lib/browser/BrowserController.js b/src/lib/browser/BrowserController.js index 7f181cd461..03845df266 100644 --- a/src/lib/browser/BrowserController.js +++ b/src/lib/browser/BrowserController.js @@ -151,26 +151,6 @@ export default class BrowserController { } } - async setKey(key) { - let accounts = await Account.getAllAccounts() - await Promise.all(accounts.map(a => a.updateFromStorage())) - this.key = key - let hashedKey = await Cryptography.sha256(key) - let encryptedHash = await Cryptography.encryptAES( - key, - hashedKey, - 'FLOCCUS' - ) - await browser.storage.local.set({ accountsLocked: encryptedHash }) - if (accounts.length) { - await Promise.all(accounts.map(a => a.setData(a.getData()))) - } - - // ...aand unlock it immediately. - this.unlocked = true - this.setEnabled(true) - } - async unlock(key) { let d = await browser.storage.local.get({ 'accountsLocked': null }) if (d.accountsLocked) { @@ -190,22 +170,16 @@ export default class BrowserController { this.setEnabled(true) // remove encryption - this.unsetKey() - } - - async unsetKey() { - if (!this.unlocked) { - throw new Error('Cannot disable encryption without unlocking first') + this.key = null + await browser.storage.local.set({ accountsLocked: null }) + const accountIds = BrowserAccountStorage.getAllAccounts() + for (let accountId of accountIds) { + const storage = new BrowserAccountStorage(accountId) + const data = await storage.getAccountData(key) + await storage.setAccountData(data, null) } let accounts = await BrowserAccount.getAllAccounts() await Promise.all(accounts.map(a => a.updateFromStorage())) - this.key = null - await browser.storage.local.set({ accountsLocked: null }) - await Promise.all(accounts.map(a => a.setData(a.getData()))) - } - - getKey() { - return Promise.resolve(this.key) } getUnlocked() { diff --git a/src/lib/interfaces/Controller.ts b/src/lib/interfaces/Controller.ts index fd03e0d5b2..9856b2cf7e 100644 --- a/src/lib/interfaces/Controller.ts +++ b/src/lib/interfaces/Controller.ts @@ -1,13 +1,10 @@ export default interface IController { setEnabled(enabled:boolean): void; - setKey(key):Promise; unlock(key):Promise; - unsetKey():Promise; scheduleSync(accountId, wait):Promise; cancelSync(accountId, keepEnabled):Promise; syncAccount(accountId, strategy):Promise; onStatusChange(listener):()=>void; - getKey():Promise; getUnlocked():Promise; onLoad():void; } diff --git a/src/lib/native/NativeAccount.ts b/src/lib/native/NativeAccount.ts index d77c1ad21e..1df69e77cf 100644 --- a/src/lib/native/NativeAccount.ts +++ b/src/lib/native/NativeAccount.ts @@ -3,7 +3,6 @@ import NativeTree from './NativeTree' import AdapterFactory from '../AdapterFactory' import Account from '../Account' import { IAccountData } from '../interfaces/AccountStorage' -import Controller from '../Controller' import { CreateBookmarkError, FailsafeError, FloccusError, @@ -19,9 +18,7 @@ import { i18n } from './I18n' export default class NativeAccount extends Account { static async get(id:string):Promise { const storage = new NativeAccountStorage(id) - const controller = await Controller.getSingleton() - const key = await controller.getKey() - const data = await storage.getAccountData(key) + const data = await storage.getAccountData(null) const tree = new NativeTree(storage) await tree.load() return new NativeAccount(id, storage, await AdapterFactory.factory(data), tree) @@ -32,9 +29,7 @@ export default class NativeAccount extends Account { const adapter = await AdapterFactory.factory(data) const storage = new NativeAccountStorage(id) - const controller = await Controller.getSingleton() - const key = await controller.getKey() - await storage.setAccountData(data, key) + await storage.setAccountData(data, null) const tree = new NativeTree(storage) await tree.load() return new NativeAccount(id, storage, adapter, tree) diff --git a/src/lib/native/NativeController.js b/src/lib/native/NativeController.js index d68159556d..c3e72b841c 100644 --- a/src/lib/native/NativeController.js +++ b/src/lib/native/NativeController.js @@ -68,26 +68,6 @@ export default class NativeController { this.enabled = enabled } - async setKey(key) { - let accounts = await Account.getAllAccounts() - await Promise.all(accounts.map(a => a.updateFromStorage())) - this.key = key - let hashedKey = await Cryptography.sha256(key) - let encryptedHash = await Cryptography.encryptAES( - key, - hashedKey, - 'FLOCCUS' - ) - await Storage.set({ key: 'accountsLocked', value: encryptedHash }) - if (accounts.length) { - await Promise.all(accounts.map(a => a.setData(a.getData()))) - } - - // ...aand unlock it immediately. - this.unlocked = true - this.setEnabled(true) - } - async unlock(key) { let accountsLocked = await Storage.get({ key: 'accountsLocked' }) if (accountsLocked) { @@ -107,21 +87,6 @@ export default class NativeController { this.setEnabled(true) } - async unsetKey() { - if (!this.unlocked) { - throw new Error('Cannot disable encryption without unlocking first') - } - let accounts = await Account.getAllAccounts() - await Promise.all(accounts.map(a => a.updateFromStorage())) - this.key = null - await Storage.set({ key: 'accountsLocked', value: null }) - await Promise.all(accounts.map(a => a.setData(a.getData()))) - } - - getKey() { - return Promise.resolve(this.key) - } - getUnlocked() { return Promise.resolve(this.unlocked) } diff --git a/src/ui/App.vue b/src/ui/App.vue index 35d723250a..c0aa5690ed 100644 --- a/src/ui/App.vue +++ b/src/ui/App.vue @@ -87,9 +87,6 @@ export default { locked() { return this.$store.state.locked }, - secured() { - return this.$store.state.secured - }, routes() { return routes }, diff --git a/src/ui/NativeApp.vue b/src/ui/NativeApp.vue index c87b5e58eb..aae476548b 100644 --- a/src/ui/NativeApp.vue +++ b/src/ui/NativeApp.vue @@ -23,9 +23,6 @@ export default { locked() { return false }, - secured() { - return false - }, background() { return this.$vuetify.theme.dark ? '#000' : '#fff' } diff --git a/src/ui/router.js b/src/ui/router.js index 202d70c5f2..a8d6901f3b 100644 --- a/src/ui/router.js +++ b/src/ui/router.js @@ -2,7 +2,6 @@ import Vue from 'vue' import Router from 'vue-router' import Overview from './views/Overview' import NewAccount from './views/NewAccount' -import SetKey from './views/SetKey' import Update from './views/Update' import ImportExport from './views/ImportExport' import Donate from './views/Donate' @@ -45,11 +44,6 @@ export const router = new Router({ name: 'ABOUT', component: About, }, - { - path: '/set-key', - name: routes.SET_KEY, - component: SetKey, - }, { path: '/update', name: routes.UPDATE, diff --git a/src/ui/store/actions.js b/src/ui/store/actions.js index ba9673c6df..c5e95fe1fc 100644 --- a/src/ui/store/actions.js +++ b/src/ui/store/actions.js @@ -10,10 +10,8 @@ import { Base64 } from 'js-base64' export const actionsDefinition = { async [actions.LOAD_LOCKED]({ commit, dispatch, state }) { const controller = await Controller.getSingleton() - const key = await controller.getKey() const unlocked = await controller.getUnlocked() commit(mutations.SET_LOCKED, !unlocked) - commit(mutations.SET_SECURED, typeof key === 'string' || !unlocked) }, async [actions.UNLOCK]({commit, dispatch, state}, key) { const controller = await Controller.getSingleton() @@ -25,14 +23,6 @@ export const actionsDefinition = { } commit(mutations.SET_LOCKED, false) }, - async [actions.SET_KEY]({commit, dispatch, state}, key) { - const controller = await Controller.getSingleton() - await controller.setKey(key) - }, - async [actions.UNSET_KEY]({commit, dispatch, state}) { - const controller = await Controller.getSingleton() - await controller.unsetKey() - }, async [actions.LOAD_ACCOUNTS]({ commit, dispatch, state }) { commit(mutations.LOADING_START, 'accounts') const accountsArray = await Account.getAllAccounts() diff --git a/src/ui/store/definitions.js b/src/ui/store/definitions.js index 392d96a0ed..d3bdcc2338 100644 --- a/src/ui/store/definitions.js +++ b/src/ui/store/definitions.js @@ -36,7 +36,6 @@ export const mutations = { LOADING_START: 'LOADING_START', LOADING_END: 'LOADING_END', SET_LOCKED: 'SET_LOCKED', - SET_SECURED: 'SET_SECURED', LOAD_ACCOUNTS: 'LOAD_ACCOUNTS', STORE_ACCOUNT_DATA: 'STORE_ACCOUNT_DATA', REMOVE_ACCOUNT: 'REMOVE_ACCOUNT', diff --git a/src/ui/store/index.js b/src/ui/store/index.js index 9f9d31fe68..bf48e8a7bb 100644 --- a/src/ui/store/index.js +++ b/src/ui/store/index.js @@ -13,7 +13,6 @@ export default new Store({ actions: actionsDefinition, state: { locked: false, - secured: false, accounts: {}, loginFlow: { isRunning: false diff --git a/src/ui/store/mutations.js b/src/ui/store/mutations.js index 312e38c78d..066c803bfe 100644 --- a/src/ui/store/mutations.js +++ b/src/ui/store/mutations.js @@ -5,9 +5,6 @@ export const mutationsDefinition = { [mutations.SET_LOCKED](state, locked) { state.locked = locked }, - [mutations.SET_SECURED](state, secured) { - state.secured = secured - }, [mutations.LOAD_ACCOUNTS](state, accounts) { state.accounts = accounts }, diff --git a/src/ui/store/native/index.js b/src/ui/store/native/index.js index 288e6ba2eb..d61eca9e02 100644 --- a/src/ui/store/native/index.js +++ b/src/ui/store/native/index.js @@ -12,7 +12,6 @@ export default new Store({ actions: actionsDefinition, state: { locked: false, - secured: false, accounts: {}, tree: null, loginFlow: { diff --git a/src/ui/store/native/mutations.js b/src/ui/store/native/mutations.js index a48155cbe9..eec378a164 100644 --- a/src/ui/store/native/mutations.js +++ b/src/ui/store/native/mutations.js @@ -5,9 +5,6 @@ export const mutationsDefinition = { [mutations.SET_LOCKED](state, locked) { state.locked = locked }, - [mutations.SET_SECURED](state, secured) { - state.secured = secured - }, [mutations.LOAD_ACCOUNTS](state, accounts) { state.accounts = accounts }, diff --git a/src/ui/views/SetKey.vue b/src/ui/views/SetKey.vue deleted file mode 100644 index 892815ad0b..0000000000 --- a/src/ui/views/SetKey.vue +++ /dev/null @@ -1,102 +0,0 @@ - - - - - From 6e76cdd9bd4e1c716774af07377ce082f081822b Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 22 Aug 2023 15:50:19 +0200 Subject: [PATCH 34/46] Fix webdav test names --- src/test/test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/test.js b/src/test/test.js index 336bc6c6d3..689bd4ddae 100644 --- a/src/test/test.js +++ b/src/test/test.js @@ -99,12 +99,14 @@ describe('Floccus', function() { type: 'webdav', url: `${SERVER}/remote.php/webdav/`, bookmark_file: 'bookmarks.xbel', + bookmark_file_type: 'xml', ...CREDENTIALS }, { type: 'webdav', url: `${SERVER}/remote.php/webdav/`, bookmark_file: 'bookmarks.xbel', + bookmark_file_type: 'xml', passphrase: random.float(), ...CREDENTIALS }, From c752b11a6f5111ab23cf13951a1bbc8d4070b4e6 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 22 Aug 2023 15:58:04 +0200 Subject: [PATCH 35/46] CI: default tests to ncbm@stable12 --- .github/workflows/tests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8c26606c87..cf7ce8c3db 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,8 +38,8 @@ jobs: matrix: node-version: [14.x] npm-version: [7.x] - server-version: ['26'] - app-version: ['stable'] + server-version: ['25'] + app-version: ['stable12'] floccus-adapter: - fake - nextcloud-bookmarks @@ -55,8 +55,8 @@ jobs: - benchmark root browsers: ['firefox'] include: - - app-version: stable12 - server-version: 25 + - app-version: stable + server-version: 26 floccus-adapter: nextcloud-bookmarks test-name: test browsers: firefox From 9f4fc304bcfc7a9bbaa9ddbb85f38a4964e9aac7 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 22 Aug 2023 16:10:33 +0200 Subject: [PATCH 36/46] selenium-runner.js: Don't log page source --- test/selenium-runner.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/selenium-runner.js b/test/selenium-runner.js index 1d65ff22f1..1c22d36219 100644 --- a/test/selenium-runner.js +++ b/test/selenium-runner.js @@ -77,7 +77,6 @@ installConsoleHandler() // Enable permission await driver.get('about:addons') await driver.sleep(10000) - console.log(await driver.getPageSource()) await driver.executeScript(function() { document.querySelector('button[name=extension]').click() }) From 74b92f425286ff13dd36a54d9050f1e6048910ef Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 22 Aug 2023 16:11:09 +0200 Subject: [PATCH 37/46] NextcloudBookmarks: Do not write lock after onSyncCompleted --- src/lib/adapters/NextcloudBookmarks.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/adapters/NextcloudBookmarks.ts b/src/lib/adapters/NextcloudBookmarks.ts index 90ed0ec93c..a9a01076c0 100644 --- a/src/lib/adapters/NextcloudBookmarks.ts +++ b/src/lib/adapters/NextcloudBookmarks.ts @@ -74,6 +74,7 @@ export default class NextcloudBookmarksAdapter implements Adapter, BulkImportRes private canceled = false private cancelCallback: () => void = null private lockingInterval: any + private ended = false constructor(server: NextcloudBookmarksConfig) { this.server = server @@ -156,15 +157,18 @@ export default class NextcloudBookmarksAdapter implements Adapter, BulkImportRes await this.timeout(base ** i * 1000) } } - this.lockingInterval = setInterval(() => this.acquireLock(), LOCK_INTERVAL) + this.ended = false + this.lockingInterval = setInterval(() => !this.ended && this.acquireLock(), LOCK_INTERVAL) } async onSyncComplete(): Promise { + this.ended = true clearInterval(this.lockingInterval) await this.releaseLock() } async onSyncFail(): Promise { + this.ended = true clearInterval(this.lockingInterval) await this.releaseLock() } From 6a72109cfa13a39cd69bfccc9d096bd0f04b8d4a Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 22 Aug 2023 17:55:56 +0200 Subject: [PATCH 38/46] tests: Always check whether a sync run errored --- src/test/test.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/test/test.js b/src/test/test.js index 689bd4ddae..a51af3e46c 100644 --- a/src/test/test.js +++ b/src/test/test.js @@ -329,6 +329,7 @@ describe('Floccus', function() { parentId: barFolder.id }) await account.sync() // propagate to server + expect(account.getData().error).to.not.be.ok const newData = { title: 'blah' } await browser.bookmarks.update(bookmark.id, newData) @@ -383,6 +384,7 @@ describe('Floccus', function() { parentId: barFolder.id }) await account.sync() // propagate to server + expect(account.getData().error).to.not.be.ok await browser.bookmarks.remove(bookmark.id) await account.sync() // update on server @@ -433,6 +435,7 @@ describe('Floccus', function() { parentId: barFolder.id }) await account.sync() // propagate to server + expect(account.getData().error).to.not.be.ok await browser.bookmarks.move(barFolder.id, { parentId: localRoot }) await account.sync() // update on server @@ -706,6 +709,7 @@ describe('Floccus', function() { parentId: barFolder.id }) await account.sync() // propagate to server + expect(account.getData().error).to.not.be.ok await browser.bookmarks.move(barFolder.id, { parentId: localRoot }) await account.sync() // update on server @@ -780,7 +784,6 @@ describe('Floccus', function() { }) await account.sync() // propagate to server - expect(account.getData().error).to.not.be.ok // Sync again, so client can deduplicate @@ -838,6 +841,7 @@ describe('Floccus', function() { }) await account.sync() // propagate to server + expect(account.getData().error).to.not.be.ok expect(account.getData().error).to.not.be.ok @@ -895,6 +899,7 @@ describe('Floccus', function() { parentId: barFolder.id }) await account.sync() // propagate to server + expect(account.getData().error).to.not.be.ok await browser.bookmarks.move(barFolder.id, { parentId: localRoot }) await browser.bookmarks.move(fooFolder.id, { @@ -975,6 +980,7 @@ describe('Floccus', function() { parentId: barFolder.id }) await account.sync() // propagate to server + expect(account.getData().error).to.not.be.ok await browser.bookmarks.move(barFolder.id, { parentId: localRoot }) await browser.bookmarks.move(fooFolder.id, { @@ -1159,6 +1165,7 @@ describe('Floccus', function() { if (adapter.onSyncComplete) await adapter.onSyncComplete() await account.sync() // propagate creation + expect(account.getData().error).to.not.be.ok await withSyncConnection(account, async() => { await adapter.removeBookmark({...serverMark, id: serverMarkId}) @@ -2384,6 +2391,8 @@ describe('Floccus', function() { parentId: barFolder.id }) await account.sync() // propagate to server + expect(account.getData().error).to.not.be.ok + const originalTree = await getAllBookmarks(account) await account.setData({ ...account.getData(), strategy: 'slave' }) @@ -2419,6 +2428,8 @@ describe('Floccus', function() { parentId: barFolder.id }) await account.sync() // propagate to server + expect(account.getData().error).to.not.be.ok + const originalTree = await getAllBookmarks(account) await account.setData({ ...account.getData(), strategy: 'slave' }) @@ -2458,6 +2469,8 @@ describe('Floccus', function() { parentId: barFolder.id }) await account.sync() // propagate to server + expect(account.getData().error).to.not.be.ok + const originalTree = await getAllBookmarks(account) await account.setData({ ...account.getData(), strategy: 'slave' }) @@ -2541,6 +2554,7 @@ describe('Floccus', function() { if (adapter.onSyncComplete) await adapter.onSyncComplete() await account.sync() // propage creation + expect(account.getData().error).to.not.be.ok const newServerMark = { ...serverMark, @@ -2598,6 +2612,7 @@ describe('Floccus', function() { if (adapter.onSyncComplete) await adapter.onSyncComplete() await account.sync() // propage creation + expect(account.getData().error).to.not.be.ok await withSyncConnection(account, async() => { await adapter.removeBookmark({...serverMark, id: serverMarkId}) @@ -2855,6 +2870,8 @@ describe('Floccus', function() { parentId: barFolder.id }) await account.sync() // propagate to server + expect(account.getData().error).to.not.be.ok + await account.setData({ ...account.getData(), strategy: 'overwrite' @@ -2894,6 +2911,8 @@ describe('Floccus', function() { parentId: barFolder.id }) await account.sync() // propagate to server + expect(account.getData().error).to.not.be.ok + await account.setData({ ...account.getData(), strategy: 'overwrite' @@ -2937,6 +2956,8 @@ describe('Floccus', function() { parentId: barFolder.id }) await account.sync() // propagate to server + expect(account.getData().error).to.not.be.ok + await account.setData({ ...account.getData(), strategy: 'overwrite' @@ -3008,6 +3029,7 @@ describe('Floccus', function() { if (adapter.onSyncComplete) await adapter.onSyncComplete() await account.sync() // propage creation + expect(account.getData().error).to.not.be.ok const originalTree = await account.localTree.getBookmarksTree(true) await account.setData({ ...account.getData(), @@ -3051,6 +3073,7 @@ describe('Floccus', function() { if (adapter.onSyncComplete) await adapter.onSyncComplete() await account.sync() // propage creation + expect(account.getData().error).to.not.be.ok const originalTree = await account.localTree.getBookmarksTree(true) await account.setData({ ...account.getData(), From 0874d70752f44d13bb7265bc7a24bff46d6ffe73 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 23 Aug 2023 11:07:19 +0200 Subject: [PATCH 39/46] Fix test "sync root folder ignoring unsupported folders" --- src/test/test.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/test/test.js b/src/test/test.js index a51af3e46c..65d26119e8 100644 --- a/src/test/test.js +++ b/src/test/test.js @@ -2175,6 +2175,11 @@ describe('Floccus', function() { }) it('should sync root folder ignoring unsupported folders', async function() { const [root] = await browser.bookmarks.getTree() + + await Promise.all( + root.children.flatMap(child => child.children.map(child => browser.bookmarks.removeTree(child.id))) + ) + const originalFolderId = account.getData().localRoot await account.setData({...account.getData(), localRoot: root.id, }) account = await Account.get(account.id) @@ -2200,6 +2205,7 @@ describe('Floccus', function() { bookmark = {...serverMark, id} }) + const secondBookmarkFolderTitle = root.children[0].title await browser.bookmarks.create({ title: 'url', url: 'http://ur.l/', @@ -2217,7 +2223,7 @@ describe('Floccus', function() { bookmark.parentId = serverTree.children.find(folder => folder.title !== 'foo').id const fooFolder = serverTree.children.find(folder => folder.title === 'foo') await adapter.updateBookmark(new Bookmark(bookmark)) - const secondBookmark = serverTree.children.filter(folder => folder.title !== 'foo')[0].children.find(item => item.type === 'bookmark') + const secondBookmark = serverTree.children.find(folder => folder.title === secondBookmarkFolderTitle).children.find(item => item.type === 'bookmark') secondBookmark.parentId = fooFolder.id await adapter.updateBookmark(secondBookmark) }) @@ -3098,7 +3104,7 @@ describe('Floccus', function() { }) }) context('with two clients', function() { - this.timeout(60 * 60000) // timeout after 20mins + this.timeout(40 * 60000) // timeout after 20mins let account1, account2 beforeEach('set up accounts', async function() { account1 = await Account.create(ACCOUNT_DATA) From 3795a62af5415716347a9c6b0ea183828ef9c601 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 23 Aug 2023 13:33:51 +0200 Subject: [PATCH 40/46] Tests: Increase default timeout to 120s to accomodate nc26 :/ --- src/test/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/test.js b/src/test/test.js index 65d26119e8..5d2d1fde23 100644 --- a/src/test/test.js +++ b/src/test/test.js @@ -54,7 +54,7 @@ let expectTreeEqual = function(tree1, tree2, ignoreEmptyFolders, checkOrder = tr } describe('Floccus', function() { - this.timeout(60000) // no test should run longer than 60s + this.timeout(120000) // no test should run longer than 120s this.slow(20000) // 20s is slow const params = (new URL(window.location.href)).searchParams From 19e1580070ccb8661a35f813252f3c7c0a607a1f Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 23 Aug 2023 15:34:34 +0200 Subject: [PATCH 41/46] Fix test: sync root folder ignoring unsupported folders --- src/test/test.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/test/test.js b/src/test/test.js index 5d2d1fde23..e5c8b08cd4 100644 --- a/src/test/test.js +++ b/src/test/test.js @@ -2649,6 +2649,11 @@ describe('Floccus', function() { }) it('should sync root folder ignoring unsupported folders', async function() { const [root] = await browser.bookmarks.getTree() + + await Promise.all( + root.children.flatMap(child => child.children.map(child => browser.bookmarks.removeTree(child.id))) + ) + const originalFolderId = account.getData().localRoot await account.setData({...account.getData(), localRoot: root.id, }) account = await Account.get(account.id) @@ -2674,6 +2679,7 @@ describe('Floccus', function() { bookmark = {...serverMark, id} }) + const secondBookmarkFolderTitle = root.children[0].title await browser.bookmarks.create({ title: 'url', url: 'http://ur.l/', @@ -2691,7 +2697,7 @@ describe('Floccus', function() { bookmark.parentId = serverTree.children.find(folder => folder.title !== 'foo').id const fooFolder = serverTree.children.find(folder => folder.title === 'foo') await adapter.updateBookmark(new Bookmark(bookmark)) - const secondBookmark = serverTree.children.filter(folder => folder.title !== 'foo')[0].children.find(item => item.type === 'bookmark') + const secondBookmark = serverTree.children.find(folder => folder.title === secondBookmarkFolderTitle).children.find(item => item.type === 'bookmark') secondBookmark.parentId = fooFolder.id await adapter.updateBookmark(secondBookmark) }) From 5ea4efb97dffd44799526bf768e562715e2ac36e Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 23 Aug 2023 16:24:59 +0200 Subject: [PATCH 42/46] gulpfile: Distinguish between build for chrome and build for firefox --- gulpfile.js | 43 ++++++++++++++++++++++++++++++------- manifest.chrome.json | 40 +++++++++++++++++++++++++++++++++++ manifest.firefox.json | 47 +++++++++++++++++++++++++++++++++++++++++ test/selenium-runner.js | 2 +- 4 files changed, 123 insertions(+), 9 deletions(-) create mode 100644 manifest.chrome.json create mode 100644 manifest.firefox.json diff --git a/gulpfile.js b/gulpfile.js index 83f1df8fc6..1d7036b8ca 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -44,6 +44,7 @@ const paths = { '!key.pem', '!android/**', '!ios/**', + '!manifest*.json' ], views: './html/*.html', nativeHTML: './html/index.html', @@ -53,6 +54,10 @@ const paths = { icons: 'icons/*', dist: './dist/**' } + +paths.chromeZip = [...paths.zip, 'manifest.chrome.json'] +paths.firefoxZip = [...paths.zip, 'manifest.firefox.json'] + const WEBSTORE_ID = 'fnaicdffflnofjppbagibeoednhnbjhg' let WEBSTORE_CREDENTIALS @@ -120,23 +125,45 @@ const build = gulp.parallel(assets, js) const main = gulp.series(build, native) -const zip = function() { +const chromeZip = function() { return gulp - .src(paths.zip, { buffer: false }) - .pipe(gulpZip(`floccus-build-v${VERSION}.zip`)) + .src(paths.chromeZip, { buffer: false }) + .pipe(rename((path) => { + if (path.basename.startsWith('manifest') && path.extname === '.json') { + path.basename = 'manifest' + } + })) + .pipe(gulpZip(`floccus-build-v${VERSION}-chrome.zip`)) + .pipe(gulp.dest(paths.builds)) +} + +const firefoxZip = function() { + return gulp + .src(paths.firefoxZip, { buffer: false }) + .pipe(rename((path) => { + if (path.basename.startsWith('manifest') && path.extname === '.json') { + path.basename = 'manifest' + } + })) + .pipe(gulpZip(`floccus-build-v${VERSION}-firefox.zip`)) .pipe(gulp.dest(paths.builds)) } const xpi = function() { return gulp - .src(paths.zip, { buffer: false }) + .src(paths.firefoxZip, { buffer: false }) + .pipe(rename((path) => { + if (path.basename.startsWith('manifest') && path.extname === '.json') { + path.basename = 'manifest' + } + })) .pipe(gulpZip(`floccus-build-v${VERSION}.xpi`)) .pipe(gulp.dest(paths.builds)) } const crx = function() { return crx3( - fs.createReadStream(`${paths.builds}/floccus-build-v${VERSION}.zip`), + fs.createReadStream(`${paths.builds}/floccus-build-v${VERSION}-chrome.zip`), { keyPath: 'key.pem', crxPath: `${paths.builds}/floccus-build-v${VERSION}.crx`, @@ -144,12 +171,12 @@ const crx = function() { ) } -const release = gulp.series(main, gulp.parallel(zip, xpi), crx) +const release = gulp.series(main, gulp.parallel(firefoxZip, chromeZip, xpi), crx) -const publish = gulp.series(main, zip, function() { +const publish = gulp.series(main, chromeZip, function() { return webstore .uploadExisting( - fs.createReadStream(`${paths.builds}floccus-build-v${VERSION}.zip`) + fs.createReadStream(`${paths.builds}floccus-build-v${VERSION}-chrome.zip`) ) .then(function() { return webstore.publish('default') diff --git a/manifest.chrome.json b/manifest.chrome.json new file mode 100644 index 0000000000..928a03dba4 --- /dev/null +++ b/manifest.chrome.json @@ -0,0 +1,40 @@ +{ + "manifest_version": 3, + "name": "floccus bookmarks sync", + "short_name": "floccus", + "version": "5.0.0", + "description": "__MSG_DescriptionExtension__", + "icons": { + "48": "icons/logo.png", + "64": "icons/logo_64.png", + "128": "icons/logo_128.png" + }, + + "default_locale": "en", + + "permissions": ["alarms", "bookmarks", "storage", "unlimitedStorage", "tabs", "identity"], + "host_permissions": [ + "*://*/*" + ], + "content_security_policy": { + "extension_pages": "script-src 'self'; object-src 'self';" + }, + + "options_ui": { + "page": "dist/html/options.html", + "browser_style": false + }, + + "action": { + "browser_style": false, + "default_icon": { + "48": "icons/logo.png" + }, + "default_title": "Open Floccus options", + "default_popup": "dist/html/options.html" + }, + + "background": { + "service_worker": "dist/js/background-script.js" + } +} diff --git a/manifest.firefox.json b/manifest.firefox.json new file mode 100644 index 0000000000..82b117331e --- /dev/null +++ b/manifest.firefox.json @@ -0,0 +1,47 @@ +{ + "manifest_version": 3, + "name": "floccus bookmarks sync", + "short_name": "floccus", + "version": "5.0.0", + "description": "__MSG_DescriptionExtension__", + "icons": { + "48": "icons/logo.png", + "64": "icons/logo_64.png", + "128": "icons/logo_128.png" + }, + + "applications": { + "gecko": { + "id": "floccus@handmadeideas.org", + "strict_min_version": "57.0" + } + }, + + "default_locale": "en", + + "permissions": ["alarms", "bookmarks", "storage", "unlimitedStorage", "tabs", "identity"], + "host_permissions": [ + "*://*/*" + ], + "content_security_policy": { + "extension_pages": "script-src 'self'; object-src 'self';" + }, + + "options_ui": { + "page": "dist/html/options.html", + "browser_style": false + }, + + "action": { + "browser_style": false, + "default_icon": { + "48": "icons/logo.png" + }, + "default_title": "Open Floccus options", + "default_popup": "dist/html/options.html" + }, + + "background": { + "scripts": ["dist/js/background-script.js"] + } +} diff --git a/test/selenium-runner.js b/test/selenium-runner.js index 1c22d36219..13fed51f62 100644 --- a/test/selenium-runner.js +++ b/test/selenium-runner.js @@ -60,7 +60,7 @@ installConsoleHandler() case 'firefox': // Scrape extension id from firefox addons page await driver.installAddon( - `${__dirname}/../builds/floccus-build-v${VERSION}.zip`, + `${__dirname}/../builds/floccus-build-v${VERSION}-firefox.zip`, true ) From 03cde5a53d4bba2a6c5916b153c18546fa41b3c4 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 23 Aug 2023 16:35:52 +0200 Subject: [PATCH 43/46] CI: test in chrome --- .github/workflows/tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cf7ce8c3db..cddaad36e6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -53,7 +53,9 @@ jobs: test-name: - test - benchmark root - browsers: ['firefox'] + browsers: + - firefox + - chrome include: - app-version: stable server-version: 26 From 135d0c93da3bc3f88e25bb5a6e4f2fbce26d3c5f Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 23 Aug 2023 16:42:39 +0200 Subject: [PATCH 44/46] CI: test in chrome --- test/selenium-runner.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/test/selenium-runner.js b/test/selenium-runner.js index 13fed51f62..593c3aa72a 100644 --- a/test/selenium-runner.js +++ b/test/selenium-runner.js @@ -26,12 +26,7 @@ installConsoleHandler() '--no-sandbox', // see https://bugs.chromium.org/p/chromedriver/issues/detail?id=2473 '--remote-debugging-port=9222' ]) - .addExtensions( - fs.readFileSync( - `./builds/floccus-build-v${VERSION}.crx`, - 'base64' - ) - ) + .addExtensions(`./builds/floccus-build-v${VERSION}.crx`) : null ) .setLoggingPrefs(loggingPrefs) From 57f760fa8b7d9f8eef525a8243e3610352a01483 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 23 Aug 2023 17:13:25 +0200 Subject: [PATCH 45/46] Tests: Fix tests in chrome --- src/test/test.js | 17 ++++++++++++++--- test/selenium-runner.js | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/test/test.js b/src/test/test.js index e5c8b08cd4..2592a750e3 100644 --- a/src/test/test.js +++ b/src/test/test.js @@ -58,7 +58,7 @@ describe('Floccus', function() { this.slow(20000) // 20s is slow const params = (new URL(window.location.href)).searchParams - let SERVER, CREDENTIALS, ACCOUNTS, APP_VERSION, SEED + let SERVER, CREDENTIALS, ACCOUNTS, APP_VERSION, SEED, BROWSER SERVER = params.get('server') || 'http://localhost' @@ -67,6 +67,7 @@ describe('Floccus', function() { password: params.get('password') || 'admin' } APP_VERSION = params.get('app_version') || 'stable' + BROWSER = params.get('browser') || 'firefox' SEED = (new URL(window.location.href)).searchParams.get('seed') || Math.random() + '' console.log('RANDOMNESS SEED', SEED) @@ -1904,6 +1905,10 @@ describe('Floccus', function() { this.skip() return } + if (BROWSER !== 'firefox') { + this.skip() + return + } const localRoot = account.getData().localRoot expect( @@ -2031,6 +2036,10 @@ describe('Floccus', function() { this.skip() return } + if (BROWSER !== 'firefox') { + this.skip() + return + } const localRoot = account.getData().localRoot expect( @@ -2223,7 +2232,8 @@ describe('Floccus', function() { bookmark.parentId = serverTree.children.find(folder => folder.title !== 'foo').id const fooFolder = serverTree.children.find(folder => folder.title === 'foo') await adapter.updateBookmark(new Bookmark(bookmark)) - const secondBookmark = serverTree.children.find(folder => folder.title === secondBookmarkFolderTitle).children.find(item => item.type === 'bookmark') + // toLowerCase to accomodate chrome (since we normalize the title) + const secondBookmark = serverTree.children.find(folder => folder.title.toLowerCase() === secondBookmarkFolderTitle.toLowerCase()).children.find(item => item.type === 'bookmark') secondBookmark.parentId = fooFolder.id await adapter.updateBookmark(secondBookmark) }) @@ -2697,7 +2707,8 @@ describe('Floccus', function() { bookmark.parentId = serverTree.children.find(folder => folder.title !== 'foo').id const fooFolder = serverTree.children.find(folder => folder.title === 'foo') await adapter.updateBookmark(new Bookmark(bookmark)) - const secondBookmark = serverTree.children.find(folder => folder.title === secondBookmarkFolderTitle).children.find(item => item.type === 'bookmark') + // toLowerCase to accomodate chrome (since we normalize the title) + const secondBookmark = serverTree.children.find(folder => folder.title.toLowerCase() === secondBookmarkFolderTitle.toLowerCase()).children.find(item => item.type === 'bookmark') secondBookmark.parentId = fooFolder.id await adapter.updateBookmark(secondBookmark) }) diff --git a/test/selenium-runner.js b/test/selenium-runner.js index 593c3aa72a..43e73692f6 100644 --- a/test/selenium-runner.js +++ b/test/selenium-runner.js @@ -94,7 +94,7 @@ installConsoleHandler() throw new Error('Unknown browser') } - testUrl += `dist/html/test.html?grep=${process.env.FLOCCUS_TEST}&server=http://${process.env.TEST_HOST}&app_version=${process.env.APP_VERSION}` + testUrl += `dist/html/test.html?grep=${process.env.FLOCCUS_TEST}&server=http://${process.env.TEST_HOST}&app_version=${process.env.APP_VERSION}&browser=${process.env.SELENIUM_BROWSER}` if (process.env.FLOCCUS_TEST.includes('google-drive')) { testUrl += `&password=${process.env.GOOGLE_API_REFRESH_TOKEN}` From 995a53b1b1cfc0aadb9203345ace49cf0baa1c9b Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Fri, 25 Aug 2023 16:18:46 +0200 Subject: [PATCH 46/46] XbelSerializer: Use fast-xml-parser instead of DOMParser to allow it to function in service worker --- package-lock.json | 40 ++++++++++++ package.json | 1 + src/lib/adapters/WebDav.ts | 29 ++++++--- src/lib/serializers/Xbel.ts | 123 +++++++++++++++++------------------- src/test/test.js | 4 +- 5 files changed, 122 insertions(+), 75 deletions(-) diff --git a/package-lock.json b/package-lock.json index 332a90a1de..f82cd2cfcc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "cordova-plugin-foreground-service": "^1.1.3", "cordova-plugin-inappbrowser": "^5.0.0", "core-js": "3.x", + "fast-xml-parser": "^4.2.7", "humanize-duration": "^3.25.1", "intl-messageformat": "^9.9.1", "js-base64": "^3.7.5", @@ -6917,6 +6918,27 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-xml-parser": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.7.tgz", + "integrity": "sha512-J8r6BriSLO1uj2miOk1NW0YVm8AGOOu3Si2HQp/cSmo6EA4m3fcwu2WKjJ4RK9wMLBtg69y1kS8baDiQBR41Ig==", + "funding": [ + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + }, + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", @@ -14148,6 +14170,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -21538,6 +21565,14 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "fast-xml-parser": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.7.tgz", + "integrity": "sha512-J8r6BriSLO1uj2miOk1NW0YVm8AGOOu3Si2HQp/cSmo6EA4m3fcwu2WKjJ4RK9wMLBtg69y1kS8baDiQBR41Ig==", + "requires": { + "strnum": "^1.0.5" + } + }, "fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", @@ -27050,6 +27085,11 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", diff --git a/package.json b/package.json index 392f0327bd..41324639d2 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "cordova-plugin-foreground-service": "^1.1.3", "cordova-plugin-inappbrowser": "^5.0.0", "core-js": "3.x", + "fast-xml-parser": "^4.2.7", "humanize-duration": "^3.25.1", "intl-messageformat": "^9.9.1", "js-base64": "^3.7.5", diff --git a/src/lib/adapters/WebDav.ts b/src/lib/adapters/WebDav.ts index 40e16daf9d..a891739968 100644 --- a/src/lib/adapters/WebDav.ts +++ b/src/lib/adapters/WebDav.ts @@ -145,16 +145,27 @@ export default class WebDavAdapter extends CachingAdapter { let res, lockFreed, i = 0 try { do { - res = await Http.request({ - url: fullUrl, - method: 'DELETE', - headers: { - Authorization: 'Basic ' + authString - }, - webFetchExtra: { + if (Capacitor.getPlatform() === 'web') { + res = await fetch(fullUrl, { + method: 'DELETE', credentials: 'omit', - } - }) + headers: { + Authorization: 'Basic ' + authString + }, + ...(!this.server.allowRedirects && {redirect: 'manual'}), + }) + } else { + res = await Http.request({ + url: fullUrl, + method: 'DELETE', + headers: { + Authorization: 'Basic ' + authString + }, + webFetchExtra: { + credentials: 'omit', + } + }) + } lockFreed = res.status === 200 || res.status === 204 || res.status === 404 if (!lockFreed) { await this.timeout(1000) diff --git a/src/lib/serializers/Xbel.ts b/src/lib/serializers/Xbel.ts index 0be9e6adef..580f9ecc98 100644 --- a/src/lib/serializers/Xbel.ts +++ b/src/lib/serializers/Xbel.ts @@ -1,95 +1,90 @@ import Serializer from '../interfaces/Serializer' import { Bookmark, Folder, ItemLocation } from '../Tree' +import { XMLParser, XMLBuilder } from 'fast-xml-parser' class XbelSerializer implements Serializer { - serialize(folder) { - return this._serializeFolder(folder, '') + serialize(folder: Folder) { + const xbelObj = this._serializeFolder(folder) + const xmlBuilder = new XMLBuilder({format: true, preserveOrder: true, ignoreAttributes: false}) + return xmlBuilder.build(xbelObj) } - deserialize(xbel) { - const xmlDoc = new DOMParser().parseFromString( - xbel, - 'application/xml' - ) - const nodeList = xmlDoc.getElementsByTagName('xbel') - if (!nodeList.length) { + deserialize(xbel: string) { + const parser = new XMLParser({preserveOrder: true, ignorePiTags: true, ignoreAttributes: false}) + const xmlObj = parser.parse(xbel) + + if (!Array.isArray(xmlObj[0].xbel)) { throw new Error( - 'Parse Error: ' + new XMLSerializer().serializeToString(xmlDoc) + 'Parse Error: ' + xbel ) } const rootFolder = new Folder({ id: 0, title: 'root', location: ItemLocation.SERVER }) - this._parseFolder(nodeList[0], rootFolder) + try { + this._parseFolder(xmlObj[0].xbel, rootFolder) + } catch (e) { + throw new Error( + 'Parse Error: ' + e.message + ) + } return rootFolder } - _parseFolder(xbelObj, folder) { + _parseFolder(xbelObj, folder: Folder) { /* parse depth first */ - xbelObj.childNodes.forEach(node => { - let item - if (node.tagName && node.tagName === 'bookmark') { - item = new Bookmark({ - id: parseInt(node.id), - parentId: folder.id, - url: node.getAttribute('href'), - title: node.firstElementChild.textContent, - location: ItemLocation.SERVER, - }) - } else if (node.tagName && node.tagName === 'folder') { - item = new Folder({ - id: parseInt(node.getAttribute('id')), - title: node.firstElementChild.textContent, - parentId: folder.id, - location: ItemLocation.SERVER, - }) - this._parseFolder(node, item) - } else { - return - } + xbelObj + .forEach(node => { + let item + if (typeof node.bookmark !== 'undefined') { + item = new Bookmark({ + id: parseInt(node[':@']['@_id']), + parentId: folder.id, + url: node[':@']['@_href'], + title: node.bookmark[0]['#text'], + location: ItemLocation.SERVER, + }) + } else if (typeof node.folder !== 'undefined') { + item = new Folder({ + id: parseInt(node[':@']['@_id']), + title: node[':@']['@_title'], + parentId: folder.id, + location: ItemLocation.SERVER, + }) + this._parseFolder(node.folder, item) + } else { + return + } - folder.children.push(item) - }) + folder.children.push(item) + }) } - _serializeFolder(folder, indent) { - /* Dummy XML document so we can create XML Elements */ - const xmlDocument = new DOMParser().parseFromString( - '', - 'application/xml' - ) - + _serializeFolder(folder: Folder) { return folder.children .map(child => { if (child instanceof Bookmark) { - const bookmark = xmlDocument.createElement('bookmark') - bookmark.setAttribute('href', child.url) - bookmark.setAttribute('id', String(child.id)) - const title = xmlDocument.createElement('title') - title.textContent = child.title - bookmark.appendChild(title) - return new XMLSerializer().serializeToString( - bookmark - ) + return { + bookmark: [ + {'#text': child.title} + ], + ':@': { + '@_href': child.url, + '@_id': String(child.id) + } + } } if (child instanceof Folder) { - const folder = xmlDocument.createElement('folder') - if ('id' in child) { - folder.setAttribute('id', String(child.id)) + return { + folder: this._serializeFolder(child), + ':@': { + ...('id' in child && {'@_id': String(child.id)}), + '@_title': child.title, + } } - - const title = xmlDocument.createElement('title') - title.textContent = child.title - folder.appendChild(title) - - folder.innerHTML += this._serializeFolder(child, indent + ' ') - return new XMLSerializer().serializeToString( - folder - ) } }) - .join('\r\n' + indent) } } diff --git a/src/test/test.js b/src/test/test.js index 2592a750e3..29ff605601 100644 --- a/src/test/test.js +++ b/src/test/test.js @@ -100,14 +100,14 @@ describe('Floccus', function() { type: 'webdav', url: `${SERVER}/remote.php/webdav/`, bookmark_file: 'bookmarks.xbel', - bookmark_file_type: 'xml', + bookmark_file_type: 'xbel', ...CREDENTIALS }, { type: 'webdav', url: `${SERVER}/remote.php/webdav/`, bookmark_file: 'bookmarks.xbel', - bookmark_file_type: 'xml', + bookmark_file_type: 'xbel', passphrase: random.float(), ...CREDENTIALS },