From 2720b0cff42f5a9dcdcca07017b985229810c103 Mon Sep 17 00:00:00 2001 From: Vladimir Y <1560781+vladimiry@users.noreply.github.com> Date: Tue, 16 Nov 2021 00:30:45 +0300 Subject: [PATCH] update dependencies --- .eslintrc | 16 + .github/workflows/main.yml | 6 +- package.json | 83 +- ....6.patch => app-builder-lib+22.14.7.patch} | 0 scripts/typescript-upgrade.ts | 18 + .../database/export/service.ts | 10 +- .../database/indexing/api.ts | 2 +- .../database/indexing/service.ts | 2 +- .../endpoints-builders/database/search/api.ts | 2 +- .../api/endpoints-builders/proton-session.ts | 9 +- src/electron-main/tray.ts | 2 +- src/electron-main/web-request/service.ts | 34 +- .../lib/hovered-href-highlighter/index.ts | 2 +- src/electron-preload/lib/spell-check.ts | 2 +- .../primary/provider-api/rate-limiting.ts | 2 +- .../main-process/actions/dbIndexerRequest.ts | 4 +- .../main-process/actions/dbIndexerResponse.ts | 4 +- .../api/main-process/actions/notification.ts | 4 +- src/shared/api/main-process/index.ts | 7 +- src/shared/ngrx-util-of-type.ts | 112 +++ src/shared/ngrx-util.ts | 36 +- .../app/_accounts/account.component.ts | 6 +- .../app/_accounts/accounts.effects.ts | 7 +- .../app/_accounts/accounts.service.ts | 3 +- .../browser-window/app/_core/core.service.ts | 3 +- .../app/_core/navigation.effects.ts | 3 +- .../app/_db-view/db-view.effects.ts | 7 +- .../notification-item.component.html | 14 +- .../notification-item.component.ts | 2 +- .../app/_notification/notification.effects.ts | 3 +- .../app/_options/options.effects.ts | 5 +- src/web/browser-window/app/app.module.ts | 23 +- .../app/store/actions/accounts.ts | 4 +- .../app/store/actions/db-view.ts | 4 +- .../app/store/actions/navigation.ts | 3 +- .../app/store/actions/notification.ts | 18 +- .../app/store/actions/options.ts | 3 +- .../app/store/reducers/notification.ts | 12 +- .../app/store/reducers/options.ts | 4 +- .../app/store/selectors/options.ts | 2 +- src/web/browser-window/vendor/vendor.scss | 2 +- src/web/lib/native-theme.ts | 2 +- tsconfig.json | 2 +- yarn.lock | 901 +++++++++--------- 44 files changed, 789 insertions(+), 601 deletions(-) rename patches/patch-package/{app-builder-lib+22.14.6.patch => app-builder-lib+22.14.7.patch} (100%) create mode 100644 scripts/typescript-upgrade.ts create mode 100644 src/shared/ngrx-util-of-type.ts diff --git a/.eslintrc b/.eslintrc index e14baff40..92a677e79 100644 --- a/.eslintrc +++ b/.eslintrc @@ -42,6 +42,22 @@ "patterns": [ "rxjs/*", "!rxjs/operators" + ], + "paths": [ + { + "name": "@ngrx/store", + "importNames": [ + "props" + ], + "message": "Import \"props\" from \"src/shared/ngrx-util\" instead." + }, + { + "name": "@ngrx/effects", + "importNames": [ + "ofType" + ], + "message": "Import \"ofType\" from \"src/shared/ngrx-util-of-type\" instead." + } ] } ], diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ffa84598e..2103b4954 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,7 +12,7 @@ jobs: runs-on: ${{ matrix.os }} steps: # TODO use YAML anchors feature when it gets supported - - { uses: actions/setup-node@v1, with: { node-version: "${{ env.ELECTRON_MAIL_NODE_VERSION }}" } } + - { uses: actions/setup-node@v2, with: { node-version: "${{ env.ELECTRON_MAIL_NODE_VERSION }}" } } - { uses: actions/checkout@v2 } - { name: system setup, if: runner.os == 'Linux', run: ./scripts/ci/github/system-setup-linux.sh } - name: get yarn cache directory path @@ -45,7 +45,7 @@ jobs: runs-on: ${{ matrix.os }} steps: # TODO use YAML anchors feature when it gets supported - - { uses: actions/setup-node@v1, with: { node-version: "${{ env.ELECTRON_MAIL_NODE_VERSION }}" } } + - { uses: actions/setup-node@v2, with: { node-version: "${{ env.ELECTRON_MAIL_NODE_VERSION }}" } } - { uses: actions/checkout@v2 } - { name: system setup, if: runner.os == 'Linux', run: ./scripts/ci/github/system-setup-linux.sh } - name: get yarn cache directory path @@ -71,7 +71,7 @@ jobs: runs-on: ${{ matrix.os }} steps: # TODO use YAML anchors feature when it gets supported - - { uses: actions/setup-node@v1, with: { node-version: "${{ env.ELECTRON_MAIL_NODE_VERSION }}" } } + - { uses: actions/setup-node@v2, with: { node-version: "${{ env.ELECTRON_MAIL_NODE_VERSION }}" } } - { uses: actions/checkout@v2 } - { name: system setup, if: runner.os == 'Linux', run: ./scripts/ci/github/system-setup-linux.sh } - name: get yarn cache directory path diff --git a/package.json b/package.json index 41277ea2a..b3015837c 100644 --- a/package.json +++ b/package.json @@ -18,13 +18,14 @@ "type": "module", "main": "./app/electron-main/index.cjs", "scripts": { - "prepare": "npm-run-all prepare:patch-package prepare:husky-install prepare:remove:prebuild-install prepare:ts-patch scripts/code-generation/electron-main", + "prepare": "npm-run-all prepare:patch-package prepare:husky-install prepare:remove:prebuild-install prepare:ts-patch prepare:ts-patch:check scripts/code-generation/electron-main", "prepare:patch-package": "patch-package --error-on-fail --patch-dir ./patches/patch-package", "prepare:husky-install": "husky install", "prepare:ts-patch": "ts-patch install -s", + "prepare:ts-patch:check": "ts-patch check", "prepare:remove:prebuild-install": "rimraf ./node_modules/prebuild-install", "yarn:audit": "yarn audit", - "app:dist": "npm-run-all lint build assets build:electron-builder-hooks clean:prebuilds electron-builder:install-app-deps", + "app:dist": "npm-run-all lint build assets build:electron-builder-hooks clean:prebuilds", "build": "npm-run-all build:electron-main build:electron-preload build:web", "build:dev": "cross-env NODE_ENV=development npm-run-all build", "build:electron-main": "npm-run-all scripts/code-generation/electron-main build:electron-main:compile", @@ -53,8 +54,10 @@ "build:web:search": "yarn webpack:shortcut --config ./webpack-configs/web/search-in-page-browser-view.ts", "build:web:search:dev": "cross-env NODE_ENV=development npm-run-all build:web:search", "build:electron-builder-hooks:celanup": "rimraf ./scripts/electron-builder/hooks/afterPack/index.js", + "build:electron-builder-hooks:pre-build": "yarn ts-node:shortcut ./scripts/typescript-upgrade.ts upgrade", "build:electron-builder-hooks:build": "yarn webpack:shortcut --config ./webpack-configs/electron-builder-hooks.ts", - "build:electron-builder-hooks": "npm-run-all build:electron-builder-hooks:celanup build:electron-builder-hooks:build", + "build:electron-builder-hooks:after-build": "yarn ts-node:shortcut ./scripts/typescript-upgrade.ts rollback", + "build:electron-builder-hooks": "npm-run-all build:electron-builder-hooks:celanup build:electron-builder-hooks:pre-build build:electron-builder-hooks:build build:electron-builder-hooks:after-build", "clean:app": "rimraf ./app", "clean:app-dev": "rimraf ./app-dev", "clean:output": "rimraf ./output", @@ -68,9 +71,7 @@ "assets:webclient:base": "yarn ts-node:shortcut ./scripts/prepare-webclient/index.ts", "electron-builder:dist": "npm run electron-builder:shortcut -- --x64 --publish never", "electron-builder:dist:linux:appimage": "yarn ts-node:shortcut ./scripts/electron-builder/build-appimage.ts", - "electron-builder:dist:linux:appimage:do": "npm run electron-builder:shortcut -- --x64 --publish never --linux appimage", "electron-builder:dist:linux:snap": "yarn ts-node:shortcut ./scripts/electron-builder/build-snap.ts", - "electron-builder:dist:linux:snap:do": "npm run electron-builder:shortcut -- --x64 --publish never --linux snap", "electron-builder:dist:linux:deb": "npm run electron-builder:shortcut -- --x64 --publish never --linux deb", "electron-builder:dist:linux:freebsd": "npm run electron-builder:shortcut -- --x64 --publish never --linux freebsd", "electron-builder:dist:linux:pacman": "npm run electron-builder:shortcut -- --x64 --publish never --linux pacman", @@ -101,32 +102,32 @@ "spellchecker": "https://github.com/vladimiry-playground/node-spellchecker#37cebc43ccbfce27dac51f64ec45f72d9aa80b8f" }, "devDependencies": { - "@angular-devkit/build-optimizer": "0.1300.2", - "@angular-eslint/eslint-plugin": "13.0.0-alpha.0", - "@angular/animations": "13.0.1", - "@angular/cdk": "13.0.0", - "@angular/common": "13.0.1", - "@angular/compiler": "13.0.1", - "@angular/compiler-cli": "13.0.1", - "@angular/core": "13.0.1", - "@angular/forms": "13.0.1", - "@angular/language-service": "13.0.1", - "@angular/platform-browser": "13.0.1", - "@angular/platform-browser-dynamic": "13.0.1", - "@angular/router": "13.0.1", + "@angular-devkit/build-optimizer": "0.1300.3", + "@angular-eslint/eslint-plugin": "13.0.1", + "@angular/animations": "13.0.2", + "@angular/cdk": "13.0.2", + "@angular/common": "13.0.2", + "@angular/compiler": "13.0.2", + "@angular/compiler-cli": "13.0.2", + "@angular/core": "13.0.2", + "@angular/forms": "13.0.2", + "@angular/language-service": "13.0.2", + "@angular/platform-browser": "13.0.2", + "@angular/platform-browser-dynamic": "13.0.2", + "@angular/router": "13.0.2", "@babel/core": "7.16.0", - "@babel/plugin-proposal-async-generator-functions": "7.16.0", + "@babel/plugin-proposal-async-generator-functions": "7.16.4", "@babel/plugin-transform-async-to-generator": "7.16.0", - "@babel/plugin-transform-runtime": "7.16.0", + "@babel/plugin-transform-runtime": "7.16.4", "@babel/runtime": "7.16.3", "@cliqz/url-parser": "1.1.4", "@expo/spawn-async": "1.5.0", "@msgpack/msgpack": "2.7.1", - "@ng-select/ng-select": "7.3.0", - "@ngrx/component": "12.5.1", - "@ngrx/effects": "12.5.1", - "@ngrx/store": "12.5.1", - "@ngtools/webpack": "13.0.2", + "@ng-select/ng-select": "8.1.1", + "@ngrx/component": "13.0.1", + "@ngrx/effects": "13.0.1", + "@ngrx/store": "13.0.1", + "@ngtools/webpack": "13.0.3", "@playwright/test": "1.16.3", "@rgba-image/lanczos": "0.1.1", "@types/archiver": "5.1.1", @@ -135,14 +136,14 @@ "@types/fs-extra": "9.0.13", "@types/html-to-text": "8.0.1", "@types/mini-css-extract-plugin": "2.4.0", - "@types/node": "16.11.7", + "@types/node": "16.11.9", "@types/oboe": "2.1.1", "@types/opentype.js": "1.3.3", "@types/path-is-inside": "1.0.0", "@types/randomstring": "1.1.8", - "@types/react": "17.0.34", + "@types/react": "17.0.35", "@types/react-router": "5.1.17", - "@types/readable-stream": "2.3.11", + "@types/readable-stream": "2.3.12", "@types/rimraf": "3.0.2", "@types/sanitize-html": "2.5.0", "@types/semver": "7.3.9", @@ -151,13 +152,13 @@ "@types/tough-cookie": "4.0.1", "@types/ts-nameof": "4.2.1", "@types/valid-url": "1.0.3", - "@types/validator": "13.6.6", + "@types/validator": "13.7.0", "@types/webpack-node-externals": "2.5.3", - "@typescript-eslint/eslint-plugin": "5.3.1", - "@typescript-eslint/parser": "5.3.1", + "@typescript-eslint/eslint-plugin": "5.4.0", + "@typescript-eslint/parser": "5.4.0", "@vladimiry/import-sort-style": "0.1.4", "@zerollup/ts-transform-paths": "1.7.18", - "app-builder-lib": "22.14.6", + "app-builder-lib": "22.14.7", "archiver": "5.3.0", "asap-es": "1.3.3", "babel-loader": "8.2.3", @@ -168,13 +169,13 @@ "class-validator": "0.13.1", "color-fns": "0.1.1", "combine-errors": "3.0.3", - "compare-versions": "4.0.1", + "compare-versions": "4.1.1", "cpx2": "4.0.0", "cross-env": "7.0.3", "css-loader": "6.5.1", "dts-generator": "3.0.0", - "electron": "16.0.0-beta.8", - "electron-builder": "22.14.3", + "electron": "15.3.2", + "electron-builder": "22.14.7", "electron-fetch": "1.7.4", "electron-log": "4.4.1", "electron-unhandled": "3.0.2", @@ -196,9 +197,9 @@ "import-sort-parser-typescript": "6.0.0", "imports-loader": "3.1.1", "js-base64": "3.7.2", - "lint-staged": "11.2.6", + "lint-staged": "12.0.3", "lzutf8": "0.6.0", - "mini-css-extract-plugin": "2.4.4", + "mini-css-extract-plugin": "2.4.5", "monaco-editor": "0.30.1", "ndx": "1.0.2", "ndx-query": "1.0.1", @@ -215,7 +216,7 @@ "postcss-loader": "6.2.0", "postcss-url": "10.1.3", "pure-uuid": "1.6.2", - "pureimage": "0.3.5", + "pureimage": "0.3.6", "quickjs-emscripten": "0.13.0", "randomstring": "1.2.1", "rate-limiter-flexible": "2.3.4", @@ -228,7 +229,7 @@ "sass": "1.43.4", "sass-loader": "12.3.0", "semver": "7.3.5", - "serialize-error": "8.1.0", + "serialize-error": "9.0.0", "sinon": "12.0.1", "terser-webpack-plugin": "5.2.5", "to-string-loader": "1.2.0", @@ -243,9 +244,9 @@ "ts-patch": "2.0.1", "tsconfig-paths": "3.11.0", "tslib": "2.3.1", - "typescript": "4.4.4", + "typescript": "4.5.2", "valid-url": "1.0.9", - "webpack": "5.63.0", + "webpack": "5.64.1", "webpack-cli": "4.9.1", "webpack-merge": "5.8.0", "webpack-node-externals": "3.0.0", diff --git a/patches/patch-package/app-builder-lib+22.14.6.patch b/patches/patch-package/app-builder-lib+22.14.7.patch similarity index 100% rename from patches/patch-package/app-builder-lib+22.14.6.patch rename to patches/patch-package/app-builder-lib+22.14.7.patch diff --git a/scripts/typescript-upgrade.ts b/scripts/typescript-upgrade.ts new file mode 100644 index 000000000..456906700 --- /dev/null +++ b/scripts/typescript-upgrade.ts @@ -0,0 +1,18 @@ +// TODO drop "scripts/typescript-upgrade.ts" after typescript gets updated to newer than 4.5.2 version +// see https://github.com/microsoft/TypeScript/pull/46818 + +import {catchTopLeventAsync, execShell} from "scripts/lib"; + +const [, , ACTION_TYPE_ARG] = process.argv as [null, null, "upgrade" | "rollback" | unknown]; + +catchTopLeventAsync(async () => { + if (ACTION_TYPE_ARG === "upgrade") { + await execShell(["yarn", ["add", "--dev", "typescript@4.6.0-dev.20211119"]]); + return; + } + if (ACTION_TYPE_ARG === "rollback") { + await execShell(["yarn", ["add", "--dev", "typescript@4.5.2"]]); + return; + } + throw new Error(`Unexpected action type argument: ${String(ACTION_TYPE_ARG)}`); +}); diff --git a/src/electron-main/api/endpoints-builders/database/export/service.ts b/src/electron-main/api/endpoints-builders/database/export/service.ts index 4c62cb171..072448be5 100644 --- a/src/electron-main/api/endpoints-builders/database/export/service.ts +++ b/src/electron-main/api/endpoints-builders/database/export/service.ts @@ -149,10 +149,8 @@ const formatEmlDate: (mail: Mail) => string = (() => { })(); // TODO consider sanitizing "mail.body" -const contentBuilders: Readonly, -) => string>> = { +const contentBuilders: Record<"eml" | "json", + (mail: DeepReadonly, attachmentsContent?: DeepReadonly) => string> = { eml(mail, attachmentsContent) { const mixedBoundary = `=mixed-${new UUID(4).format()}@${PACKAGE_NAME}`; const relatedBoundary = `=related-${new UUID(4).format()}@${PACKAGE_NAME}`; @@ -243,11 +241,11 @@ const contentBuilders: Readonly + attachments?: DbExportMailAttachmentItem[] }> ): Promise<{ file: string }> => { const file = await generateFileName(options.mail, options.exportDir, options.fileType); diff --git a/src/electron-main/api/endpoints-builders/database/indexing/api.ts b/src/electron-main/api/endpoints-builders/database/indexing/api.ts index 42d0ab4ea..9cccb7ffb 100644 --- a/src/electron-main/api/endpoints-builders/database/indexing/api.ts +++ b/src/electron-main/api/endpoints-builders/database/indexing/api.ts @@ -1,7 +1,6 @@ import electronLog from "electron-log"; import {defer, lastValueFrom} from "rxjs"; import {filter, first, startWith, takeUntil} from "rxjs/operators"; -import {ofType} from "@ngrx/effects"; import {Context} from "src/electron-main/model"; import { @@ -17,6 +16,7 @@ import { import {IpcMainApiEndpoints,} from "src/shared/api/main-process"; import {curryFunctionMembers} from "src/shared/util"; import {indexAccount} from "src/electron-main/api/endpoints-builders/database/indexing/service"; +import {ofType} from "src/shared/ngrx-util-of-type"; const logger = curryFunctionMembers(electronLog, __filename); diff --git a/src/electron-main/api/endpoints-builders/database/indexing/service.ts b/src/electron-main/api/endpoints-builders/database/indexing/service.ts index 6dc697948..bda326a20 100644 --- a/src/electron-main/api/endpoints-builders/database/indexing/service.ts +++ b/src/electron-main/api/endpoints-builders/database/indexing/service.ts @@ -2,7 +2,6 @@ import UUID from "pure-uuid"; import electronLog from "electron-log"; import {concatMap, filter, first} from "rxjs/operators"; import {lastValueFrom, race, throwError, timer} from "rxjs"; -import {ofType} from "@ngrx/effects"; import {pick} from "remeda"; import {Config} from "src/shared/model/options"; @@ -13,6 +12,7 @@ import {UnionOf} from "src/shared/ngrx-util"; import {curryFunctionMembers} from "src/shared/util"; import {hrtimeDuration} from "src/electron-main/util"; import {readMailBody} from "src/shared/entity-util"; +import {ofType} from "src/shared/ngrx-util-of-type"; const logger = curryFunctionMembers(electronLog, __filename); diff --git a/src/electron-main/api/endpoints-builders/database/search/api.ts b/src/electron-main/api/endpoints-builders/database/search/api.ts index ec72ac19a..e0a1e5258 100644 --- a/src/electron-main/api/endpoints-builders/database/search/api.ts +++ b/src/electron-main/api/endpoints-builders/database/search/api.ts @@ -2,7 +2,6 @@ import UUID from "pure-uuid"; import electronLog from "electron-log"; import {from, lastValueFrom, Observable, of, race, throwError, timer} from "rxjs"; import {concatMap, filter, first, mergeMap, switchMap} from "rxjs/operators"; -import {ofType} from "@ngrx/effects"; import {Context} from "src/electron-main/model"; import {IPC_MAIN_API_DB_INDEXER_REQUEST$, IPC_MAIN_API_DB_INDEXER_RESPONSE$} from "src/electron-main/api/constants"; @@ -11,6 +10,7 @@ import {IndexableMailId} from "src/shared/model/database"; import {IpcMainApiEndpoints} from "src/shared/api/main-process"; import {curryFunctionMembers} from "src/shared/util"; import {searchRootConversationNodes, secondSearchStep} from "src/electron-main/api/endpoints-builders/database/search/service"; +import {ofType} from "src/shared/ngrx-util-of-type"; const logger = curryFunctionMembers(electronLog, __filename); diff --git a/src/electron-main/api/endpoints-builders/proton-session.ts b/src/electron-main/api/endpoints-builders/proton-session.ts index 549a076bd..ec4411acf 100644 --- a/src/electron-main/api/endpoints-builders/proton-session.ts +++ b/src/electron-main/api/endpoints-builders/proton-session.ts @@ -100,19 +100,22 @@ export async function buildEndpoints( } const session = resolveInitializedAccountSession({login}); - - // TODO consider setting "samesite=none" cookie attribute when restoring the saved cookie to session - // currently "samesite" is not being restored or set so default value gets applied (likely to be "lax" at the moment) + const sameSiteNoneCookieAttribute: Readonly>> = { + sameSite: "no_restriction", + secure: true, // "samesite=none" attribute requires "secure" attribute + }; await Promise.all([ session.cookies.set({ ...pickTokenCookiePropsToApply(accessTokenCookie), // eslint-disable-next-line @typescript-eslint/restrict-template-expressions url: `${apiEndpointOrigin}${accessTokenCookie.path}`, + ...sameSiteNoneCookieAttribute, }), session.cookies.set({ ...pickTokenCookiePropsToApply(refreshTokenCookie), // eslint-disable-next-line @typescript-eslint/restrict-template-expressions url: `${apiEndpointOrigin}${refreshTokenCookie.path}`, + ...sameSiteNoneCookieAttribute, }), ]); diff --git a/src/electron-main/tray.ts b/src/electron-main/tray.ts index dce283a5f..94b70c08b 100644 --- a/src/electron-main/tray.ts +++ b/src/electron-main/tray.ts @@ -1,11 +1,11 @@ import {app, Menu, MenuItemConstructorOptions, nativeImage, Tray} from "electron"; import {Subscription} from "rxjs"; -import {ofType} from "@ngrx/effects"; import {tap} from "rxjs/operators"; import {IPC_MAIN_API_NOTIFICATION$} from "src/electron-main/api/constants"; import {IPC_MAIN_API_NOTIFICATION_ACTIONS} from "src/shared/api/main-process/actions"; import {IpcMainApiEndpoints} from "src/shared/api/main-process"; +import {ofType} from "src/shared/ngrx-util-of-type"; // TODO crete "endpoints"-dependent menu items in disabled state and enable on "endpoints" promise resolving export async function initTray(endpoints: Promise): Promise { diff --git a/src/electron-main/web-request/service.ts b/src/electron-main/web-request/service.ts index 9a8ac68e7..fd5ca928c 100644 --- a/src/electron-main/web-request/service.ts +++ b/src/electron-main/web-request/service.ts @@ -145,19 +145,29 @@ export const patchResponseHeaders: ( }, ); - // starting from @electron v12 (more exactly from the respective @chromium version) - // the "set-cookie" records with "samesite=strict" get blocked by @chromium, for example the "/api/auth/cookies" request case - // so to workaround the issue we replace the "samesite=strict|lax"-like attribute with "samesite=none" - for (const cookieName of Object.keys(responseHeaders)) { - if (cookieName.toLowerCase() !== "set-cookie") { - continue; - } - const cookies = responseHeaders[cookieName]; - if (cookies) { - responseHeaders[cookieName] = cookies.map((cookieValue) => { + { + // starting from @electron v12 (more exactly from the respective @chromium version) + // the "set-cookie" records with "samesite=strict" get blocked by @chromium, for example the "/api/auth/cookies" request case + // so to workaround the issue we replace the "samesite=strict|lax"-like attribute with "samesite=none" + for (const cookieName of Object.keys(responseHeaders)) { + if (cookieName.toLowerCase() !== "set-cookie") { + continue; + } + const cookies = responseHeaders[cookieName]; + if (cookies) { // TODO consider patching the "samesite" cookie attribute only for "/api/auth/cookies" request - return cookieValue.replace(/samesite[\s]*=[\s]*(strict|lax)/i, "samesite=none"); - }); + responseHeaders[cookieName] = cookies.map((cookieValue) => { + if ((/samesite[\s]*=[\s]*(strict|lax|none)/i).test(cookieValue)) { + cookieValue = cookieValue.replace(/samesite[\s]*=[\s]*(strict|lax)/i, "samesite=none"); + } else { + cookieValue = `${cookieValue}; samesite=none`; + } + cookieValue = /(;[\s]*secure)|(secure[\s]*;)/i.test(cookieValue) + ? cookieValue + : `${cookieValue}; secure`; // "samesite=none" attribute requires "secure" attribute + return cookieValue; + }); + } } } diff --git a/src/electron-preload/lib/hovered-href-highlighter/index.ts b/src/electron-preload/lib/hovered-href-highlighter/index.ts index e3084cfab..45659ae28 100644 --- a/src/electron-preload/lib/hovered-href-highlighter/index.ts +++ b/src/electron-preload/lib/hovered-href-highlighter/index.ts @@ -1,13 +1,13 @@ import {Deferred} from "ts-deferred"; import {Observable, Subscription} from "rxjs"; import {distinctUntilChanged} from "rxjs/operators"; -import {ofType} from "@ngrx/effects"; import css from "src/electron-preload/lib/hovered-href-highlighter/index.scss"; import {IPC_MAIN_API_NOTIFICATION_ACTIONS} from "src/shared/api/main-process/actions"; import {IpcMainServiceScan} from "src/shared/api/main-process"; import {ONE_SECOND_MS, PACKAGE_NAME} from "src/shared/constants"; import {buildLoggerBundle, resolveIpcMainApi} from "src/electron-preload/lib/util"; +import {ofType} from "src/shared/ngrx-util-of-type"; const [[, cssCode]] = css; const {locals: {renderVisibleClass}} = css; diff --git a/src/electron-preload/lib/spell-check.ts b/src/electron-preload/lib/spell-check.ts index 74173b648..5b68850ca 100644 --- a/src/electron-preload/lib/spell-check.ts +++ b/src/electron-preload/lib/spell-check.ts @@ -1,11 +1,11 @@ import {Deferred} from "ts-deferred"; -import {ofType} from "@ngrx/effects"; import {webFrame} from "electron"; // tslint:disable-line:no-import-zones import {IPC_MAIN_API_NOTIFICATION_ACTIONS} from "src/shared/api/main-process/actions"; import {Locale, Logger} from "src/shared/model/common"; import {ONE_SECOND_MS} from "src/shared/constants"; import {resolveIpcMainApi} from "src/electron-preload/lib/util"; +import {ofType} from "src/shared/ngrx-util-of-type"; const apiClientCleanupService = (() => { const deferred = new Deferred(); diff --git a/src/electron-preload/webview/primary/provider-api/rate-limiting.ts b/src/electron-preload/webview/primary/provider-api/rate-limiting.ts index e5610ff1f..c6208e476 100644 --- a/src/electron-preload/webview/primary/provider-api/rate-limiting.ts +++ b/src/electron-preload/webview/primary/provider-api/rate-limiting.ts @@ -79,7 +79,7 @@ export const attachRateLimiting = async (api: ProviderApi, logger_: Logger): Pro }); }; - (apiGroup as Record)[methodName] = overriddenMethod; + (apiGroup as Record)[methodName] = overriddenMethod; logger.verbose(`attached rate limiting to method: ${logMethodName}`); } diff --git a/src/shared/api/main-process/actions/dbIndexerRequest.ts b/src/shared/api/main-process/actions/dbIndexerRequest.ts index 6cac1fe5b..11f52e3f9 100644 --- a/src/shared/api/main-process/actions/dbIndexerRequest.ts +++ b/src/shared/api/main-process/actions/dbIndexerRequest.ts @@ -1,7 +1,5 @@ -import {props} from "@ngrx/store"; - import * as DbModel from "src/shared/model/database"; -import {propsRecordToActionsRecord} from "src/shared/ngrx-util"; +import {props, propsRecordToActionsRecord} from "src/shared/ngrx-util"; export const IPC_MAIN_API_DB_INDEXER_REQUEST_ACTIONS = propsRecordToActionsRecord( { diff --git a/src/shared/api/main-process/actions/dbIndexerResponse.ts b/src/shared/api/main-process/actions/dbIndexerResponse.ts index f87fbe4cf..de5794cf4 100644 --- a/src/shared/api/main-process/actions/dbIndexerResponse.ts +++ b/src/shared/api/main-process/actions/dbIndexerResponse.ts @@ -1,7 +1,5 @@ -import {props} from "@ngrx/store"; - import * as DbModel from "src/shared/model/database"; -import {propsRecordToActionsRecord} from "src/shared/ngrx-util"; +import {props, propsRecordToActionsRecord} from "src/shared/ngrx-util"; export const IPC_MAIN_API_DB_INDEXER_RESPONSE_ACTIONS = propsRecordToActionsRecord( { diff --git a/src/shared/api/main-process/actions/notification.ts b/src/shared/api/main-process/actions/notification.ts index 5ed1d85e8..d568e2284 100644 --- a/src/shared/api/main-process/actions/notification.ts +++ b/src/shared/api/main-process/actions/notification.ts @@ -1,10 +1,8 @@ -import {props} from "@ngrx/store"; - import * as DbModel from "src/shared/model/database"; import {Config} from "src/shared/model/options"; import {Controller} from "src/electron-main/spell-check/model"; import {IPC_MAIN_API_DB_INDEXER_RESPONSE_ACTIONS} from "src/shared/api/main-process/actions"; -import {propsRecordToActionsRecord, UnionOf} from "src/shared/ngrx-util"; +import {props, propsRecordToActionsRecord, UnionOf} from "src/shared/ngrx-util"; // WARN: do not put sensitive data or any data to the main process notification stream, only status-like signals export const IPC_MAIN_API_NOTIFICATION_ACTIONS = propsRecordToActionsRecord( diff --git a/src/shared/api/main-process/index.ts b/src/shared/api/main-process/index.ts index f0584418b..ca90917d6 100644 --- a/src/shared/api/main-process/index.ts +++ b/src/shared/api/main-process/index.ts @@ -79,8 +79,11 @@ export const ENDPOINTS_DEFINITION = { uuid: string; accountPk: NoExtraProps; attachments: Array>; - serializedError?: import("serialize-error").ErrorObject + & ( + | { data: Uint8Array } + | { serializedError: NoExtraProps> } + )>>; + serializedError?: NoExtraProps> }>>>(), dbSearchRootConversationNodes: diff --git a/src/shared/ngrx-util-of-type.ts b/src/shared/ngrx-util-of-type.ts new file mode 100644 index 000000000..2b3ab2ac1 --- /dev/null +++ b/src/shared/ngrx-util-of-type.ts @@ -0,0 +1,112 @@ +// picked from https://github.com/ngrx/platform/blob/fb78f7394765608ecf4718bba6a3df16a43e8913/modules/effects/src/actions.ts +// TODO enable automatic picking + +import type {Action, ActionCreator, Creator} from "@ngrx/store"; +import type {OperatorFunction} from "rxjs"; +import {filter} from "rxjs/operators"; + +// Module-private helper type +type ActionExtractor, + E> = T extends string ? E : ReturnType>; + +export function ofType[], + U extends Action = Action, + V = ReturnType>(...allowedTypes: AC): OperatorFunction; + +export function ofType, + AC extends ActionCreator, + T1 extends string | AC, + U extends Action = Action, + V = T1 extends string ? E : ReturnType>>(t1: T1): OperatorFunction; +export function ofType, + AC extends ActionCreator, + T1 extends string | AC, + T2 extends string | AC, + U extends Action = Action, + V = ActionExtractor>(t1: T1, t2: T2): OperatorFunction; +export function ofType, + AC extends ActionCreator, + T1 extends string | AC, + T2 extends string | AC, + T3 extends string | AC, + U extends Action = Action, + V = ActionExtractor>(t1: T1, t2: T2, t3: T3): OperatorFunction; +export function ofType, + AC extends ActionCreator, + T1 extends string | AC, + T2 extends string | AC, + T3 extends string | AC, + T4 extends string | AC, + U extends Action = Action, + V = ActionExtractor>(t1: T1, t2: T2, t3: T3, t4: T4): OperatorFunction; +export function ofType, + AC extends ActionCreator, + T1 extends string | AC, + T2 extends string | AC, + T3 extends string | AC, + T4 extends string | AC, + T5 extends string | AC, + U extends Action = Action, + V = ActionExtractor>(t1: T1, t2: T2, t3: T3, t4: T4, t5: T5): OperatorFunction; +/** + * Fallback for more than 5 arguments. + * There is no inference, so the return type is the same as the input - + * Observable. + * + * We provide a type parameter, even though TS will not infer it from the + * arguments, to preserve backwards compatibility with old versions of ngrx. + */ +export function ofType( + ...allowedTypes: Array> +): OperatorFunction; +/** + * `ofType` filters an Observable of `Actions` into an Observable of the actions + * whose type strings are passed to it. + * + * For example, if `actions` has type `Actions`, and + * the type of the `Addition` action is `add`, then + * `actions.pipe(ofType('add'))` returns an `Observable`. + * + * Properly typing this function is hard and requires some advanced TS tricks + * below. + * + * Type narrowing automatically works, as long as your `actions` object + * starts with a `Actions` instead of generic `Actions`. + * + * For backwards compatibility, when one passes a single type argument + * `ofType('something')` the result is an `Observable`. Note, that `T` + * completely overrides any possible inference from 'something'. + * + * Unfortunately, for unknown 'actions: Actions' these types will produce + * 'Observable'. In such cases one has to manually set the generic type + * like `actions.ofType('add')`. + * + * @usageNotes + * + * Filter the Actions stream on the "customers page loaded" action + * + * ```ts + * import { ofType } from '@ngrx/effects'; + * import * fromCustomers from '../customers'; + * + * this.actions$.pipe( + * ofType(fromCustomers.pageLoaded) + * ) + * ``` + */ +export function ofType( + ...allowedTypes: Array> +): OperatorFunction { + return filter((action: Action) => + allowedTypes.some((typeOrActionCreator) => { + if (typeof typeOrActionCreator === 'string') { + // Comparing the string to type + return typeOrActionCreator === action.type; + } + + // We are filtering by ActionCreator + return typeOrActionCreator.type === action.type; + }) + ); +} diff --git a/src/shared/ngrx-util.ts b/src/shared/ngrx-util.ts index 290407fb9..00133feed 100644 --- a/src/shared/ngrx-util.ts +++ b/src/shared/ngrx-util.ts @@ -1,10 +1,8 @@ -import type {ActionCreator, ActionCreatorProps} from "@ngrx/store"; +import type {ActionCreator, ActionCreatorProps, Creator} from "@ngrx/store"; +import type {NotAllowedInPropsCheck} from "@ngrx/store/src/models"; import type {ValueOf} from "ts-essentials"; -import {createAction} from "@ngrx/store"; import {mapKeys} from "remeda"; -// TODO make "ngrx-util" independent from @angular and use it for all @ngrx manipulations (including "ofType" calls) - type MatchPropName = "match"; type TypedAction = { readonly type: T }; @@ -21,7 +19,7 @@ type ActionsRecord

= { }; // eslint-disable-next-line @typescript-eslint/ban-types -export type UnionOf = Exclude, symbol>>>, boolean>; +export type UnionOf = Exclude, symbol | number>>>, boolean>; export type UnionOfRecord

= ActionsRecord

> = { [K in Exclude]: ReturnType }; @@ -41,6 +39,16 @@ type Match

= ( | Partial, A>> & { default: (variant: UnionOf>) => A } ) => A; +// picked from https://github.com/ngrx/platform/blob/fb78f7394765608ecf4718bba6a3df16a43e8913/modules/store/src/action_creator.ts#L128 +// TODO enable automatic picking +export const props =

>(): ActionCreatorProps

=> { + return {_as: "props", _p: undefined!}; // eslint-disable-line @typescript-eslint/no-non-null-assertion +}; + +const defineType = (type: TypedAction["type"], creator: Creator): ActionCreator => { + return Object.defineProperty(creator, 'type', {value: type, writable: false}) as ActionCreator; +}; + // TODO actions: freeze the result export const propsRecordToActionsRecord =

( value: P, @@ -56,15 +64,15 @@ export const propsRecordToActionsRecord =

( }; return { ...Object.entries(value).reduce( - (accumulator, [key, actionCreatorProps]) => ({ - ...accumulator, - [key]: actionCreatorProps - ? createAction( - resolvePrefixedType(key), - (value: typeof actionCreatorProps._p) => ({payload: value}), - ) - : createAction(resolvePrefixedType(key)), - }), + (accumulator, [key, actionCreatorProps]) => { + const type = resolvePrefixedType(key); + return { + ...accumulator, + [key]: actionCreatorProps + ? defineType(type, (value: typeof actionCreatorProps._p) => ({payload: value, type})) + : defineType(type, () => ({type})), + }; + }, {} as ActionsRecord

, ), match(value, matchers) { diff --git a/src/web/browser-window/app/_accounts/account.component.ts b/src/web/browser-window/app/_accounts/account.component.ts index 53237dc56..290634797 100644 --- a/src/web/browser-window/app/_accounts/account.component.ts +++ b/src/web/browser-window/app/_accounts/account.component.ts @@ -20,8 +20,8 @@ import { tap, withLatestFrom, } from "rxjs/operators"; -import {ofType} from "@ngrx/effects"; import {pick} from "remeda"; +import {ofType} from "src/shared/ngrx-util-of-type"; import {ACCOUNTS_ACTIONS, NAVIGATION_ACTIONS} from "src/web/browser-window/app/store/actions"; import {AccountsSelectors, OptionsSelectors} from "src/web/browser-window/app/store/selectors"; @@ -208,7 +208,7 @@ export class AccountComponent extends NgChangesObservableComponent implements On select(AccountsSelectors.FEATURED.selectedLogin), ), this.store.pipe( - select(OptionsSelectors.FEATURED.mainProcessNotification), + select(OptionsSelectors.FEATURED.mainProcessNotificationAction), ofType(IPC_MAIN_API_NOTIFICATION_ACTIONS.ActivateBrowserWindow), startWith(null), ), @@ -366,7 +366,7 @@ export class AccountComponent extends NgChangesObservableComponent implements On merge( of(null), // fired once to unblock the "combineLatest" this.store.pipe( - select(OptionsSelectors.FEATURED.mainProcessNotification), + select(OptionsSelectors.FEATURED.mainProcessNotificationAction), ofType(IPC_MAIN_API_NOTIFICATION_ACTIONS.ProtonSessionTokenCookiesModified), debounceTime(ONE_SECOND_MS), withLatestFrom(this.account$), diff --git a/src/web/browser-window/app/_accounts/accounts.effects.ts b/src/web/browser-window/app/_accounts/accounts.effects.ts index a2decd013..451cc7352 100644 --- a/src/web/browser-window/app/_accounts/accounts.effects.ts +++ b/src/web/browser-window/app/_accounts/accounts.effects.ts @@ -1,5 +1,5 @@ import type {Action} from "@ngrx/store"; -import {Actions, createEffect, ofType} from "@ngrx/effects"; +import {Actions, createEffect} from "@ngrx/effects"; import {EMPTY, Observable, concat, from, fromEvent, merge, of, race, throwError, timer} from "rxjs"; import {Store, select} from "@ngrx/store"; import { @@ -40,6 +40,7 @@ import {ONE_MINUTE_MS, ONE_SECOND_MS, PRODUCT_NAME} from "src/shared/constants"; import {State} from "src/web/browser-window/app/store/reducers/accounts"; import {consumeMemoryRateLimiter, curryFunctionMembers, isDatabaseBootstrapped, testProtonCalendarAppPage} from "src/shared/util"; import {getWebLogger} from "src/web/browser-window/util"; +import {ofType} from "src/shared/ngrx-util-of-type"; // TODO get rid of require "rate-limiter-flexible/lib/RateLimiterMemory" import // ES import makes the build fail in "web" context since webpack attempts to bundle the whole library which requires "node" context @@ -116,7 +117,7 @@ export class AccountsEffects { ), this.store.pipe( - select(OptionsSelectors.FEATURED.mainProcessNotification), + select(OptionsSelectors.FEATURED.mainProcessNotificationAction), ofType(IPC_MAIN_API_NOTIFICATION_ACTIONS.DbAttachmentExportRequest), filter(({payload: {key}}) => key.login === login), mergeMap(({payload}) => { @@ -257,7 +258,7 @@ export class AccountsEffects { // processing "db-view"-related notifications received from the main process this.store.pipe( - select(OptionsSelectors.FEATURED.mainProcessNotification), + select(OptionsSelectors.FEATURED.mainProcessNotificationAction), ofType(IPC_MAIN_API_NOTIFICATION_ACTIONS.DbPatchAccount), filter(({payload: {key}}) => key.login === login), mergeMap(({payload}) => of(ACCOUNTS_ACTIONS.Patch({login, patch: {notifications: {unread: payload.stat.unread}}}))), diff --git a/src/web/browser-window/app/_accounts/accounts.service.ts b/src/web/browser-window/app/_accounts/accounts.service.ts index 7f89a2af4..db941553f 100644 --- a/src/web/browser-window/app/_accounts/accounts.service.ts +++ b/src/web/browser-window/app/_accounts/accounts.service.ts @@ -1,4 +1,4 @@ -import {Actions, ofType} from "@ngrx/effects"; +import {Actions} from "@ngrx/effects"; import {EMPTY, Observable, merge, of, race, timer} from "rxjs"; import {Injectable} from "@angular/core"; import {Store, select} from "@ngrx/store"; @@ -13,6 +13,7 @@ import {State} from "src/web/browser-window/app/store/reducers/accounts"; import {WebAccount} from "src/web/browser-window/app/model"; import {getRandomInt} from "src/shared/util"; import {getWebLogger} from "src/web/browser-window/util"; +import {ofType} from "src/shared/ngrx-util-of-type"; @Injectable() export class AccountsService { diff --git a/src/web/browser-window/app/_core/core.service.ts b/src/web/browser-window/app/_core/core.service.ts index ef108225c..526a45945 100644 --- a/src/web/browser-window/app/_core/core.service.ts +++ b/src/web/browser-window/app/_core/core.service.ts @@ -1,6 +1,6 @@ import UUID from "pure-uuid"; import type {Action} from "@ngrx/store"; -import {Actions, ofType} from "@ngrx/effects"; +import {Actions} from "@ngrx/effects"; import {EMPTY, concat, lastValueFrom, timer} from "rxjs"; import {Injectable, NgZone} from "@angular/core"; import {Store, select} from "@ngrx/store"; @@ -18,6 +18,7 @@ import {State} from "src/web/browser-window/app/store/reducers/root"; import {WEB_CLIENTS_BLANK_HTML_FILE_NAME, WEB_VIEW_SESSION_STORAGE_KEY_SKIP_LOGIN_DELAYS,} from "src/shared/constants"; import {WebAccount} from "src/web/browser-window/app/model"; import {curryFunctionMembers, parseUrlOriginWithNullishCheck} from "src/shared/util"; +import {ofType} from "src/shared/ngrx-util-of-type"; @Injectable() export class CoreService { diff --git a/src/web/browser-window/app/_core/navigation.effects.ts b/src/web/browser-window/app/_core/navigation.effects.ts index 0e4f65f11..b954a5b74 100644 --- a/src/web/browser-window/app/_core/navigation.effects.ts +++ b/src/web/browser-window/app/_core/navigation.effects.ts @@ -1,4 +1,4 @@ -import {Actions, createEffect, ofType} from "@ngrx/effects"; +import {Actions, createEffect} from "@ngrx/effects"; import {EMPTY, from} from "rxjs"; import {Injectable, NgZone} from "@angular/core"; import {Router} from "@angular/router"; @@ -9,6 +9,7 @@ import {ElectronService} from "src/web/browser-window/app/_core/electron.service import {NAVIGATION_ACTIONS} from "src/web/browser-window/app/store/actions"; import {curryFunctionMembers} from "src/shared/util"; import {getWebLogger} from "src/web/browser-window/util"; +import {ofType} from "src/shared/ngrx-util-of-type"; const _logger = getWebLogger(__filename); diff --git a/src/web/browser-window/app/_db-view/db-view.effects.ts b/src/web/browser-window/app/_db-view/db-view.effects.ts index dd5b67efa..9dd30dd7e 100644 --- a/src/web/browser-window/app/_db-view/db-view.effects.ts +++ b/src/web/browser-window/app/_db-view/db-view.effects.ts @@ -1,5 +1,5 @@ import UUID from "pure-uuid"; -import {Actions, createEffect, ofType} from "@ngrx/effects"; +import {Actions, createEffect} from "@ngrx/effects"; import {EMPTY, forkJoin, from, merge, of} from "rxjs"; import {Store, select} from "@ngrx/store"; import {concatMap, filter, finalize, map, mergeMap, switchMap, takeUntil, tap, throttleTime, withLatestFrom} from "rxjs/operators"; @@ -14,6 +14,7 @@ import {OptionsSelectors} from "src/web/browser-window/app/store/selectors"; import {State} from "src/web/browser-window/app/store/reducers/db-view"; import {curryFunctionMembers} from "src/shared/util"; import {getWebLogger} from "src/web/browser-window/util"; +import {ofType} from "src/shared/ngrx-util-of-type"; const _logger = getWebLogger(__filename); @@ -34,7 +35,7 @@ export class DbViewEffects { from(ipcMainClient("dbGetAccountDataView")(webAccountPk)), // data load (on change in the main process) this.store.pipe( - select(OptionsSelectors.FEATURED.mainProcessNotification), + select(OptionsSelectors.FEATURED.mainProcessNotificationAction), ofType(IPC_MAIN_API_NOTIFICATION_ACTIONS.DbPatchAccount), filter(({payload: {key}}) => key.login === webAccountPk.login), filter(({payload: {entitiesModified}}) => entitiesModified), @@ -42,7 +43,7 @@ export class DbViewEffects { ), // side notification (status/progress patching) this.store.pipe( - select(OptionsSelectors.FEATURED.mainProcessNotification), + select(OptionsSelectors.FEATURED.mainProcessNotificationAction), ofType(IPC_MAIN_API_NOTIFICATION_ACTIONS.DbIndexerProgressState), filter(({payload}) => { return "key" in payload diff --git a/src/web/browser-window/app/_notification/notification-item.component.html b/src/web/browser-window/app/_notification/notification-item.component.html index f3b8c703e..3ac40d0b2 100644 --- a/src/web/browser-window/app/_notification/notification-item.component.html +++ b/src/web/browser-window/app/_notification/notification-item.component.html @@ -1,15 +1,15 @@