1
0
mirror of https://github.com/XFox111/TabsAsideExtension.git synced 2026-04-22 07:58:01 +03:00
Files
TabsAsideExtension/entrypoints/background.ts
T
xfox111 3cd3c4453d feat: Minor 3.2.0 (#197)
* chore(loc): zh_CN translation improvements (#153)

* chore(deps): Bump typescript-eslint from 8.43.0 to 8.45.0 (#166)

Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.43.0 to 8.45.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.45.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.45.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump @wxt-dev/analytics from 0.4.1 to 0.5.1 (#164)

Bumps [@wxt-dev/analytics](https://github.com/wxt-dev/wxt/tree/HEAD/packages/analytics) from 0.4.1 to 0.5.1.
- [Release notes](https://github.com/wxt-dev/wxt/releases)
- [Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/analytics/CHANGELOG.md)
- [Commits](https://github.com/wxt-dev/wxt/commits/v0.5.1/packages/analytics)

---
updated-dependencies:
- dependency-name: "@wxt-dev/analytics"
  dependency-version: 0.5.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump eslint from 9.35.0 to 9.36.0 (#163)

Bumps [eslint](https://github.com/eslint/eslint) from 9.35.0 to 9.36.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.35.0...v9.36.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.36.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump globals from 16.3.0 to 16.4.0 (#158)

Bumps [globals](https://github.com/sindresorhus/globals) from 16.3.0 to 16.4.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v16.3.0...v16.4.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-version: 16.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump @eslint/css from 0.11.0 to 0.11.1 (#157)

Bumps [@eslint/css](https://github.com/eslint/css) from 0.11.0 to 0.11.1.
- [Release notes](https://github.com/eslint/css/releases)
- [Changelog](https://github.com/eslint/css/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/css/compare/css-v0.11.0...css-v0.11.1)

---
updated-dependencies:
- dependency-name: "@eslint/css"
  dependency-version: 0.11.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump @fluentui/react-icons from 2.0.309 to 2.0.311 (#156)

Bumps [@fluentui/react-icons](https://github.com/microsoft/fluentui-system-icons) from 2.0.309 to 2.0.311.
- [Changelog](https://github.com/microsoft/fluentui-system-icons/blob/main/fluentui-android-system-icons-release.yml)
- [Commits](https://github.com/microsoft/fluentui-system-icons/commits)

---
updated-dependencies:
- dependency-name: "@fluentui/react-icons"
  dependency-version: 2.0.311
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump typescript from 5.9.2 to 5.9.3 (#165)

Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.9.2 to 5.9.3.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.9.2...v5.9.3)

---
updated-dependencies:
- dependency-name: typescript
  dependency-version: 5.9.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump vite from 7.1.5 to 7.1.7 (#161)

Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.1.5 to 7.1.7.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.1.7/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump @eslint/js from 9.35.0 to 9.36.0 (#160)

Bumps [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) from 9.35.0 to 9.36.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.36.0/packages/js)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.36.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump @stylistic/eslint-plugin from 5.3.1 to 5.4.0 (#159)

Bumps [@stylistic/eslint-plugin](https://github.com/eslint-stylistic/eslint-stylistic/tree/HEAD/packages/eslint-plugin) from 5.3.1 to 5.4.0.
- [Release notes](https://github.com/eslint-stylistic/eslint-stylistic/releases)
- [Changelog](https://github.com/eslint-stylistic/eslint-stylistic/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint-stylistic/eslint-stylistic/commits/v5.4.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@stylistic/eslint-plugin"
  dependency-version: 5.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump react-dom and @types/react-dom (#169)

Bumps [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) and [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom). These dependencies needed to be updated together.

Updates `react-dom` from 18.3.1 to 19.2.0
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.0/packages/react-dom)

Updates `@types/react-dom` from 18.3.7 to 19.2.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

---
updated-dependencies:
- dependency-name: react-dom
  dependency-version: 19.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
- dependency-name: "@types/react-dom"
  dependency-version: 19.2.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump @fluentui/react-components from 9.70.0 to 9.72.0 (#171)

Bumps [@fluentui/react-components](https://github.com/microsoft/fluentui) from 9.70.0 to 9.72.0.
- [Release notes](https://github.com/microsoft/fluentui/releases)
- [Changelog](https://github.com/microsoft/fluentui/blob/master/azure-pipelines.release.yml)
- [Commits](https://github.com/microsoft/fluentui/compare/@fluentui/react-components_v9.70.0...@fluentui/react-components_v9.72.0)

---
updated-dependencies:
- dependency-name: "@fluentui/react-components"
  dependency-version: 9.72.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump eslint from 9.36.0 to 9.37.0 (#170)

Bumps [eslint](https://github.com/eslint/eslint) from 9.36.0 to 9.37.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.36.0...v9.37.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.37.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Revert "chore(deps): Bump @wxt-dev/analytics from 0.4.1 to 0.5.1 (#164)"

This reverts commit 88178035cb.

* chore: update return type for a component

* Bump react and @types/react (#167)

Bumps [react](https://github.com/facebook/react/tree/HEAD/packages/react) and [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react). These dependencies needed to be updated together.

Updates `react` from 18.3.1 to 19.2.0
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.0/packages/react)

Updates `@types/react` from 18.3.24 to 19.2.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: react
  dependency-version: 19.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
- dependency-name: "@types/react"
  dependency-version: 19.2.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore: 3.1.1 version update

* fix(dev): regenerate lockfile

* fix: remove ts-expect-error

* fix: tabs closing before saved #178

* fix: cloud conflict error appears each time when saving a collection after removing all saved ones #180

* feat: option to disable partial save notifications #181

* fix: cloud collection storage retreival fails #180

* chore: 3.2.0 manifest version

* Bump github/codeql-action from 3 to 4 (#183)

Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump the deps group with 11 updates (#195)

Bumps the deps group with 11 updates:

| Package | From | To |
| --- | --- | --- |
| [@fluentui/react-components](https://github.com/microsoft/fluentui) | `9.72.0` | `9.72.6` |
| [@fluentui/react-icons](https://github.com/microsoft/fluentui-system-icons) | `2.0.311` | `2.0.313` |
| [@wxt-dev/analytics](https://github.com/wxt-dev/wxt/tree/HEAD/packages/analytics) | `0.4.1` | `0.5.1` |
| [@eslint/css](https://github.com/eslint/css) | `0.11.1` | `0.14.1` |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.37.0` | `9.39.1` |
| [@eslint/json](https://github.com/eslint/json) | `0.13.2` | `0.14.0` |
| [@stylistic/eslint-plugin](https://github.com/eslint-stylistic/eslint-stylistic/tree/HEAD/packages/eslint-plugin) | `5.4.0` | `5.5.0` |
| [eslint](https://github.com/eslint/eslint) | `9.37.0` | `9.39.1` |
| [globals](https://github.com/sindresorhus/globals) | `16.4.0` | `16.5.0` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.45.0` | `8.46.4` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `7.1.9` | `7.2.2` |


Updates `@fluentui/react-components` from 9.72.0 to 9.72.6
- [Release notes](https://github.com/microsoft/fluentui/releases)
- [Changelog](https://github.com/microsoft/fluentui/blob/master/azure-pipelines.release.yml)
- [Commits](https://github.com/microsoft/fluentui/compare/@fluentui/react-components_v9.72.0...@fluentui/react-components_v9.72.6)

Updates `@fluentui/react-icons` from 2.0.311 to 2.0.313
- [Changelog](https://github.com/microsoft/fluentui-system-icons/blob/main/fluentui-android-system-icons-release.yml)
- [Commits](https://github.com/microsoft/fluentui-system-icons/commits)

Updates `@wxt-dev/analytics` from 0.4.1 to 0.5.1
- [Release notes](https://github.com/wxt-dev/wxt/releases)
- [Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/analytics/CHANGELOG.md)
- [Commits](https://github.com/wxt-dev/wxt/commits/v0.5.1/packages/analytics)

Updates `@eslint/css` from 0.11.1 to 0.14.1
- [Release notes](https://github.com/eslint/css/releases)
- [Changelog](https://github.com/eslint/css/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/css/compare/css-v0.11.1...css-v0.14.1)

Updates `@eslint/js` from 9.37.0 to 9.39.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/commits/v9.39.1/packages/js)

Updates `@eslint/json` from 0.13.2 to 0.14.0
- [Release notes](https://github.com/eslint/json/releases)
- [Changelog](https://github.com/eslint/json/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/json/compare/json-v0.13.2...json-v0.14.0)

Updates `@stylistic/eslint-plugin` from 5.4.0 to 5.5.0
- [Release notes](https://github.com/eslint-stylistic/eslint-stylistic/releases)
- [Changelog](https://github.com/eslint-stylistic/eslint-stylistic/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint-stylistic/eslint-stylistic/commits/v5.5.0/packages/eslint-plugin)

Updates `eslint` from 9.37.0 to 9.39.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.37.0...v9.39.1)

Updates `globals` from 16.4.0 to 16.5.0
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v16.4.0...v16.5.0)

Updates `typescript-eslint` from 8.45.0 to 8.46.4
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.46.4/packages/typescript-eslint)

Updates `vite` from 7.1.9 to 7.2.2
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.2.2/packages/vite)

---
updated-dependencies:
- dependency-name: "@fluentui/react-components"
  dependency-version: 9.72.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: deps
- dependency-name: "@fluentui/react-icons"
  dependency-version: 2.0.313
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: deps
- dependency-name: "@wxt-dev/analytics"
  dependency-version: 0.5.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: deps
- dependency-name: "@eslint/css"
  dependency-version: 0.14.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: deps
- dependency-name: "@eslint/js"
  dependency-version: 9.39.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: deps
- dependency-name: "@eslint/json"
  dependency-version: 0.14.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: deps
- dependency-name: "@stylistic/eslint-plugin"
  dependency-version: 5.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: deps
- dependency-name: eslint
  dependency-version: 9.39.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: deps
- dependency-name: globals
  dependency-version: 16.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: deps
- dependency-name: typescript-eslint
  dependency-version: 8.46.4
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: deps
- dependency-name: vite
  dependency-version: 7.2.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: deps
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump the react group with 2 updates (#196)

Bumps the react group with 2 updates: [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) and [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom).


Updates `@types/react` from 19.2.0 to 19.2.2
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `@types/react-dom` from 19.2.0 to 19.2.2
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-version: 19.2.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: react
- dependency-name: "@types/react-dom"
  dependency-version: 19.2.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: react
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Dustin Jiang <dustinjiang@outlook.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-11 16:05:50 +03:00

518 lines
16 KiB
TypeScript

import { track, trackError } from "@/features/analytics";
import { collectionCount, getCollections, saveCollections, thumbnailCaptureEnabled } from "@/features/collectionStorage";
import { migrateStorage } from "@/features/migration";
import { setSettingsReviewNeeded } from "@/features/settingsReview/utils";
import { showWelcomeDialog } from "@/features/v3welcome/utils/showWelcomeDialog";
import { SettingsValue } from "@/hooks/useSettings";
import { CollectionItem, GraphicsStorage } from "@/models/CollectionModels";
import getLogger from "@/utils/getLogger";
import { onMessage, sendMessage } from "@/utils/messaging";
import sendNotification from "@/utils/sendNotification";
import sendPartialSaveNotification from "@/utils/sendPartialSaveNotification";
import { settings } from "@/utils/settings";
import watchTabSelection from "@/utils/watchTabSelection";
import { RemoveListenerCallback } from "@webext-core/messaging";
import { Tabs, Windows } from "wxt/browser";
import { Unwatch } from "wxt/storage";
import { openCollection, openGroup } from "./sidepanel/utils/opener";
import { closeTabsAsync } from "@/utils/closeTabsAsync";
import { getTabsToSaveAsync } from "@/utils/getTabsToSaveAsync";
import { createCollectionFromTabs } from "@/utils/createCollectionFromTabs";
import getCollectionsFromLocal from "@/features/collectionStorage/utils/getCollectionsFromLocal";
import { collectionStorage } from "@/features/collectionStorage/utils/collectionStorage";
import getCollectionsFromCloud from "@/features/collectionStorage/utils/getCollectionsFromCloud";
export default defineBackground(() =>
{
try
{
const logger = getLogger("background");
let graphicsCache: GraphicsStorage = {};
let listLocation: SettingsValue<"listLocation"> = "sidebar";
logger("Background script started");
// Little workaround for opening side panel
// See: https://stackoverflow.com/questions/77213045/error-sidepanel-open-may-only-be-called-in-response-to-a-user-gesture-re
settings.listLocation.getValue().then(location => listLocation = location);
settings.listLocation.watch(newLocation => listLocation = newLocation);
browser.runtime.onInstalled.addListener(async ({ reason, previousVersion }) =>
{
logger("onInstalled", reason, previousVersion);
track("extension_installed", { reason, previousVersion: previousVersion ?? "none" });
const [major, minor, patch] = (previousVersion ?? "0.0.0").split(".").map(parseInt);
const cumulative: number = major * 10000 + minor * 100 + patch;
await setSettingsReviewNeeded(reason, previousVersion);
if (reason === "update" && cumulative < 30000) // < 3.0.0
{
await migrateStorage();
await showWelcomeDialog.setValue(true);
browser.runtime.reload();
}
if (reason === "update" && cumulative >= 30000 && cumulative < 30200) // >= 3.0.0 && < 3.2.0
{
// Merge cloud and local storage if they are out of sync
const localTimestamp: number = await collectionStorage.localLastUpdated.getValue();
const syncTimestamp: number = await collectionStorage.syncLastUpdated.getValue();
if (localTimestamp === syncTimestamp)
return;
try
{
const localCollections: CollectionItem[] = await getCollectionsFromLocal();
const cloudCollections: CollectionItem[] = await getCollectionsFromCloud();
const mergedCollections: CollectionItem[] = [...cloudCollections, ...localCollections];
await saveCollections(mergedCollections, true, graphicsCache);
}
catch (ex)
{
logger("Failed to merge cloud and local storage during update");
trackError("cloud_sync_merge_error", ex as Error);
console.error(ex);
}
}
});
browser.commands.onCommand.addListener(
(command, tab) => performContextAction(command, tab!.windowId!)
);
onMessage("getGraphicsCache", () => graphicsCache);
onMessage("refreshCollections", () => { });
if (import.meta.env.FIREFOX)
{
onMessage("openCollection", ({ data }) => openCollection(data.collection, data.targetWindow));
onMessage("openGroup", ({ data }) => openGroup(data.group, data.newWindow));
}
setupTabCaputre();
async function setupTabCaputre(): Promise<void>
{
let unwatchAddThumbnail: RemoveListenerCallback | null = null;
let captureInterval: NodeJS.Timeout | null = null;
const captureFavicon = (_: any, __: any, tab: Tabs.Tab): void =>
{
if (!tab.url)
return;
graphicsCache[tab.url] = {
preview: graphicsCache[tab.url]?.preview,
capture: graphicsCache[tab.url]?.capture,
icon: tab.favIconUrl ?? graphicsCache[tab.url]?.icon
};
};
const tryCaptureTab = async (tab: Tabs.Tab): Promise<void> =>
{
if (!tab.url || tab.status !== "complete" || !tab.active)
return;
if (graphicsCache[tab.url]?.capture || graphicsCache[tab.url]?.capture === null)
return;
try
{
// We use chrome here because polyfill throws uncatchable errors for some reason
// It's a compatible API anyway
const capture: string = await chrome.tabs.captureVisibleTab(tab.windowId!, { format: "jpeg", quality: 1 });
if (capture)
{
graphicsCache[tab.url] = {
capture,
preview: graphicsCache[tab.url]?.preview,
icon: graphicsCache[tab.url]?.icon
};
}
}
catch
{
graphicsCache[tab.url] = {
capture: null!,
preview: graphicsCache[tab.url]?.preview,
icon: graphicsCache[tab.url]?.icon
};
}
};
const updateCapture = async (captureThumbnails: boolean): Promise<void> =>
{
const scriptingGranted: boolean = await browser.permissions.contains({ permissions: ["scripting"] });
if (captureThumbnails)
{
if (scriptingGranted)
await browser.scripting.registerContentScripts([
{
id: "capture-script",
matches: ["<all_urls>"],
runAt: "document_idle",
js: ["capture.js"]
}
]);
unwatchAddThumbnail = onMessage("addThumbnail", ({ data }) =>
{
graphicsCache[data.url] = {
preview: data.thumbnail,
capture: graphicsCache[data.url]?.capture,
icon: graphicsCache[data.url]?.icon
};
});
captureInterval = setInterval(() =>
{
browser.tabs.query({ active: true })
.then(tabs => tabs.forEach(tab => tryCaptureTab(tab)));
}, 1000);
browser.tabs.onUpdated.addListener(captureFavicon);
}
else
{
if (scriptingGranted)
await browser.scripting.unregisterContentScripts({
ids: ["capture-script"]
});
unwatchAddThumbnail?.();
if (captureInterval)
clearInterval(captureInterval);
browser.tabs.onUpdated.removeListener(captureFavicon);
graphicsCache = {};
}
};
if (await thumbnailCaptureEnabled.getValue())
updateCapture(true);
thumbnailCaptureEnabled.watch(updateCapture);
}
setupContextMenu();
async function setupContextMenu(): Promise<void>
{
await browser.contextMenus.removeAll();
const items: Record<string, string> =
{
"show_collections": i18n.t("actions.show_collections"),
"set_aside": i18n.t("actions.set_aside.all"),
"save": i18n.t("actions.save.all")
};
Object.entries(items).forEach(([id, title]) => browser.contextMenus.create({
id, title,
visible: true,
contexts: ["action"]
}));
watchTabSelection(async selection =>
{
await browser.contextMenus.update("set_aside", {
title: i18n.t(`actions.set_aside.${selection}`)
});
await browser.contextMenus.update("save", {
title: i18n.t(`actions.save.${selection}`)
});
});
browser.contextMenus.onClicked.addListener(
({ menuItemId }, tab) => performContextAction((menuItemId as string), tab!.windowId!)
);
}
setupBadge();
async function setupBadge(): Promise<void>
{
let unwatchBadge: Unwatch | null = null;
const updateBadge = async (count: number | null) =>
await browser.action.setBadgeText({ text: count && count > 0 ? count.toString() : "" });
if (await settings.showBadge.getValue())
{
updateBadge(await collectionCount.getValue());
unwatchBadge = collectionCount.watch(updateBadge);
}
if (import.meta.env.FIREFOX)
{
await browser.action.setBadgeBackgroundColor({ color: "#0f6cbd" });
await browser.action.setBadgeTextColor({ color: "white" });
}
settings.showBadge.watch(async showBadge =>
{
if (showBadge)
{
updateBadge(await collectionCount.getValue());
unwatchBadge = collectionCount.watch(updateBadge);
}
else
{
unwatchBadge?.();
await browser.action.setBadgeText({ text: "" });
}
});
}
setupActionButton();
async function setupActionButton(): Promise<void>
{
let unwatchActionTitle: Unwatch | null = null;
const onClickAction = async (): Promise<void> =>
{
logger("action.onClicked");
const defaultAction = await settings.defaultSaveAction.getValue();
await saveTabs(defaultAction === "set_aside");
};
const updateTitle = async (selection: "all" | "selected"): Promise<void> =>
{
const defaultAction = await settings.defaultSaveAction.getValue();
await browser.action.setTitle({ title: i18n.t(`actions.${defaultAction}.${selection}`) });
};
const toggleSidebarFirefox = async (): Promise<void> =>
await browser.sidebarAction.toggle();
const updateButton = async (action: SettingsValue<"contextAction">): Promise<void> =>
{
logger("updateButton", action);
// Cleanup any existing behavior
browser.action.onClicked.removeListener(onClickAction);
browser.action.onClicked.removeListener(toggleSidebarFirefox);
browser.action.onClicked.removeListener(openCollectionsInTab);
await browser.action.disable();
await browser.action.setTitle({ title: i18n.t("manifest.name") });
unwatchActionTitle?.();
if (!import.meta.env.FIREFOX)
await chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: false });
// Setup new behavior
if (action === "action")
{
browser.action.onClicked.addListener(onClickAction);
unwatchActionTitle = watchTabSelection(updateTitle);
await browser.action.enable();
}
else if (action === "open")
{
await browser.action.enable();
const location = await settings.listLocation.getValue();
if (location === "sidebar")
{
if (import.meta.env.FIREFOX)
browser.action.onClicked.addListener(toggleSidebarFirefox);
else
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
}
else if (location !== "popup")
browser.action.onClicked.addListener(openCollectionsInTab);
}
};
updateButton(await settings.contextAction.getValue());
settings.contextAction.watch(updateButton);
settings.listLocation.watch(async () => updateButton(await settings.contextAction.getValue()));
}
setupCollectionView();
async function setupCollectionView(): Promise<void>
{
const enforcePinnedTab = async (): Promise<void> =>
{
logger("enforcePinnedTab");
const openWindows: Windows.Window[] = await browser.windows.getAll({ populate: true });
for (const openWindow of openWindows)
{
if (openWindow.incognito || openWindow.type !== "normal")
continue;
const activeTabs: Tabs.Tab[] = openWindow.tabs!.filter(tab =>
tab.url === browser.runtime.getURL("/sidepanel.html"));
const targetTab: Tabs.Tab | undefined = activeTabs.find(tab => tab.pinned);
if (!targetTab)
await browser.tabs.create({
url: browser.runtime.getURL("/sidepanel.html"),
windowId: openWindow.id,
active: false,
pinned: true
});
const tabsToClose: Tabs.Tab[] = activeTabs.filter(tab => tab.id !== targetTab?.id);
if (tabsToClose.length > 0)
await browser.tabs.remove(tabsToClose.map(tab => tab.id!));
}
};
const updateView = async (viewLocation: SettingsValue<"listLocation">): Promise<void> =>
{
logger("updateView", viewLocation);
browser.tabs.onHighlighted.removeListener(enforcePinnedTab);
const tabs: Tabs.Tab[] = await browser.tabs.query({
url: browser.runtime.getURL("/sidepanel.html")
});
await browser.tabs.remove(tabs.map(tab => tab.id!));
await browser.action.setPopup({
popup: viewLocation === "popup" ? browser.runtime.getURL("/popup.html") : ""
});
if (import.meta.env.FIREFOX)
await browser.sidebarAction.setPanel({
panel: viewLocation === "sidebar" ? browser.runtime.getURL("/sidepanel.html") : ""
});
else
await chrome.sidePanel.setOptions({ enabled: viewLocation === "sidebar" });
if (viewLocation === "pinned")
{
enforcePinnedTab();
browser.tabs.onHighlighted.addListener(enforcePinnedTab);
}
};
updateView(await settings.listLocation.getValue());
settings.listLocation.watch(updateView);
}
function performContextAction(action: string, windowId: number): void
{
if (action === "show_collections")
{
if (listLocation === "sidebar" || listLocation === "popup")
openCollectionsInView(listLocation, windowId);
else
openCollectionsInTab();
}
else
saveTabs(action === "set_aside");
}
function openCollectionsInView(view: "sidebar" | "popup", windowId: number): void
{
if (view === "sidebar")
{
if (import.meta.env.FIREFOX)
browser.sidebarAction.open();
else
chrome.sidePanel.open({ windowId });
}
else
browser.action.openPopup();
}
async function openCollectionsInTab(): Promise<void>
{
logger("openCollectionsInTab");
const currentWindow: Windows.Window = await browser.windows.getCurrent({ populate: true });
if (currentWindow.incognito)
{
let availableWindows: Windows.Window[] = await browser.windows.getAll({ populate: true });
availableWindows = availableWindows.filter(window =>
!window.incognito &&
window.tabs?.some(i => i.url === browser.runtime.getURL("/sidepanel.html"))
);
if (availableWindows.length > 0)
{
const availableTab: Tabs.Tab = availableWindows[0].tabs!.find(
tab => tab.url === browser.runtime.getURL("/sidepanel.html")
)!;
await browser.tabs.update(availableTab.id, { active: true });
await browser.windows.update(availableWindows[0].id!, { focused: true });
return;
}
await browser.windows.create({
url: browser.runtime.getURL("/sidepanel.html"),
focused: true
});
}
else
{
const collectionTab: Tabs.Tab | undefined = currentWindow.tabs!.find(
tab => tab.url === browser.runtime.getURL("/sidepanel.html")
);
if (collectionTab)
await browser.tabs.update(collectionTab.id, { active: true });
else
await browser.tabs.create({
url: browser.runtime.getURL("/sidepanel.html"),
active: true,
windowId: currentWindow.id
});
}
}
async function saveTabs(closeAfterSave: boolean): Promise<void>
{
logger("saveTabs", closeAfterSave);
const [tabs, skipCount] = await getTabsToSaveAsync();
if (tabs.length < 1)
{
await sendPartialSaveNotification();
return;
}
const collection: CollectionItem = await createCollectionFromTabs(tabs);
const [savedCollections, cloudIssue] = await getCollections();
const newList = [collection, ...savedCollections];
await saveCollections(newList, cloudIssue === null, graphicsCache);
track(closeAfterSave ? "set_aside" : "save");
sendMessage("refreshCollections", undefined);
if (skipCount > 0)
await sendPartialSaveNotification();
if (closeAfterSave)
await closeTabsAsync(tabs);
if (await settings.notifyOnSave.getValue())
await sendNotification({
title: i18n.t("notifications.tabs_saved.title"),
message: i18n.t("notifications.tabs_saved.message"),
icon: "/notification_icons/cloud_checkmark.png"
});
}
}
catch (ex)
{
console.error(ex);
trackError("background_error", ex as Error);
}
});