1
0
mirror of https://github.com/XFox111/TabsAsideExtension.git synced 2026-04-22 07:58:01 +03:00

feat: Minor 3.1.0 (#150)

* Some features are now optional (#148)

* fix(dev): yarn.lock tree fix

* feat: bookmarks moved to optional permissions

* fix: analytics not working in firefox

* feat!: ability to turn off analytics (uses permissions on firefox)

* feat: analytics tracker for bookmark export

* feat: add privacy policy link in about section

* docs: privacy policy update

* feat: ability to chain multiple dialogs

* fix(loc): analytics option translation

* feat: settings review dialog

* fix: background script fails to load because of frontend code

* chore: use analytics permission as storage value

* fix: inverted analytics value

* feat!: option to disable thumbnail capture

* fix(ci): sed typo

* fix: minor fixes

* fix(firefox): web-ext lint error fix

* chore(ci): switch web-ext action

* chore(lint): fix eslint warnings

* chore(deps): monthly dependency bump (September 2025) (#149)

* chore: 3.1.0 version bump

* chore: minor cleanup

* fix: allow analytics checkbox stays inactive after denying permission on firefox

* fix(deps): yarn.lock rebuild

* fix: type assertion for userId

* fix: settings review dialog not showing if welcome dialog is not required

* fix: analytics and thumbnail capture toggles react incorrectly if permission is denied
This commit is contained in:
2025-09-09 12:24:01 +03:00
committed by GitHub
parent 735089eb59
commit e21022d985
46 changed files with 2510 additions and 2369 deletions
+12
View File
@@ -0,0 +1,12 @@
import { createAnalytics } from "@wxt-dev/analytics";
import { googleAnalytics4 } from "@wxt-dev/analytics/providers/google-analytics-4";
export const analytics = createAnalytics({
providers:
[
googleAnalytics4({
apiSecret: import.meta.env.WXT_GA4_API_SECRET,
measurementId: import.meta.env.WXT_GA4_MEASUREMENT_ID
})
]
});
@@ -0,0 +1,77 @@
import { Unwatch, WatchCallback, WxtStorageItem } from "wxt/storage";
import { analytics } from "./analytics";
import { Permissions } from "wxt/browser";
const analyticsPermission: Pick<WxtStorageItem<boolean, Record<string, unknown>>, "getValue" | "watch" | "setValue"> =
{
getValue: async (): Promise<boolean> =>
{
const isGranted: boolean = import.meta.env.FIREFOX
? await browser.permissions.contains({
// @ts-expect-error Introduced in Firefox 139
data_collection: ["technicalAndInteraction"]
})
: await allowAnalytics.getValue();
analytics.setEnabled(isGranted);
return isGranted;
},
setValue: async (value: boolean) =>
{
if (!import.meta.env.FIREFOX)
{
await allowAnalytics.setValue(value);
return;
}
let result: boolean = false;
if (value)
result = await browser.permissions.request({
// @ts-expect-error Introduced in Firefox 139
data_collection: ["technicalAndInteraction"]
});
else
result = await browser.permissions.remove({
// @ts-expect-error Introduced in Firefox 139
data_collection: ["technicalAndInteraction"]
});
if (!result)
throw new Error("Permission request was denied");
},
watch: (cb: WatchCallback<boolean>): Unwatch =>
{
if (!import.meta.env.FIREFOX)
return allowAnalytics.watch(cb);
const listener = async (permissions: Permissions.Permissions): Promise<void> =>
{
// @ts-expect-error Introduced in Firefox 139
if (permissions.data_collection?.includes("technicalAndInteraction"))
{
// @ts-expect-error Introduced in Firefox 139
const isGranted: boolean = await browser.permissions.contains({ data_collection: ["technicalAndInteraction"] });
cb(isGranted, !isGranted);
}
};
browser.permissions.onAdded.addListener(listener);
browser.permissions.onRemoved.addListener(listener);
return (): void =>
{
browser.permissions.onAdded.removeListener(listener);
browser.permissions.onRemoved.removeListener(listener);
};
}
};
export default analyticsPermission;
const allowAnalytics = storage.defineItem<boolean>("local:analytics", {
fallback: true
});
@@ -0,0 +1,30 @@
import { cloudDisabled, collectionCount } from "@/features/collectionStorage";
import { settings } from "@/utils/settings";
export async function getUserProperties(): Promise<UserProperties>
{
const properties: UserProperties =
{
cloud_used: await cloudDisabled.getValue() ? "-1" : (await browser.storage.sync.getBytesInUse() / 102400).toString(),
collection_count: (await collectionCount.getValue()).toString()
};
for (const key of Object.keys(settings))
{
const value = await settings[key as keyof typeof settings].getValue();
properties[`option_${key}`] = value.valueOf().toString();
}
return properties;
}
export const userId = storage.defineItem("local:userId", {
init: () => crypto.randomUUID()
});
export type UserProperties =
{
collection_count: string;
cloud_used: string;
[key: `option_${string}`]: string;
};
-11
View File
@@ -1,11 +0,0 @@
export default function track(eventName: string, eventProperties?: Record<string, string>): void
{
try
{
analytics.track(eventName, eventProperties);
}
catch (ex)
{
console.error("Failed to send analytics event", ex);
}
}
-15
View File
@@ -1,15 +0,0 @@
export default function trackError(name: string, error: Error): void
{
try
{
analytics.track(name, {
name: error.name,
message: error.message,
stack: error.stack ?? "no_stack"
});
}
catch (ex)
{
console.error("Failed to send error report", ex);
}
}
@@ -1,35 +0,0 @@
import { cloudDisabled, collectionCount } from "@/features/collectionStorage";
import { settings } from "@/utils/settings";
import { WxtStorageItem } from "wxt/storage";
// @ts-expect-error we don't need to implement a full storage item
const userPropertiesStorage: WxtStorageItem<Record<string, string>, any> =
{
getValue: async (): Promise<UserProperties> =>
{
console.log("userPropertiesStorage.getValue");
const properties: UserProperties =
{
cloud_used: await cloudDisabled.getValue() ? "-1" : (await browser.storage.sync.getBytesInUse() / 102400).toString(),
collection_count: (await collectionCount.getValue()).toString()
};
for (const key of Object.keys(settings))
{
const value = await settings[key as keyof typeof settings].getValue();
properties[`option_${key}`] = value.valueOf().toString();
}
return properties;
},
setValue: async () => { }
};
export default userPropertiesStorage;
export type UserProperties =
{
collection_count: string;
cloud_used: string;
[key: `option_${string}`]: string;
};