// Copyright (C) 2023 Vaughn Nugent
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
import { tabs, type Tabs } from "webextension-polyfill";
import { Watchable } from "./types";
import { defaultTo, filter, includes, isEqual } from "lodash";
import { BgRuntime, FeatureApi, IFeatureExport, exportForegroundApi, popupAndOptionsOnly } from "./framework";
import { AppSettings } from "./settings";
import { set, get, toRefs } from "@vueuse/core";
import { computed, shallowRef } from "vue";
import { waitForChangeFn } from "./util";
interface AllowedSites{
origins: string[];
enabled: boolean;
}
export interface AllowedOriginStatus{
readonly allowedOrigins: string[];
readonly enabled: boolean;
readonly currentOrigin?: string;
readonly isAllowed: boolean;
}
export interface InjectAllowlistApi extends FeatureApi, Watchable {
addOrigin(origin?: string): Promise;
removeOrigin(origin?: string): Promise;
getStatus(): Promise;
enable(value: boolean): Promise;
}
export const useInjectAllowList = (): IFeatureExport => {
return {
background: ({ state }: BgRuntime) => {
const store = state.useStorageSlot('nip07-allowlist', { origins: [], enabled: true });
const { origins, enabled } = toRefs(store)
const { currentOrigin, currentTab } = (() => {
const currentTab = shallowRef(undefined)
const currentOrigin = computed(() => currentTab.value?.url ? new URL(currentTab.value.url).origin : undefined)
//Watch for changes to the current tab
tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
//If the url changed, update the current tab
if (changeInfo.url) {
currentTab.value = tab
}
})
tabs.onActivated.addListener(async ({ tabId }) => {
//Get the tab
const tab = await tabs.get(tabId)
//Update the current tab
currentTab.value = tab
})
return { currentTab, currentOrigin }
})()
const isOriginAllowed = (origin?: string): boolean => {
//If protection is not enabled, allow all
if(enabled.value == false){
return true;
}
//if no origin specified, use current origin
origin = defaultTo(origin, currentOrigin.value)
//If no origin, return false
if (!origin) {
return false;
}
//Default to origin only
const originOnly = new URL(origin).origin
return includes(origins.value, originOnly)
}
const addOrigin = async (origin?: string): Promise => {
//if no origin specified, use current origin
const newOrigin = defaultTo(origin, currentOrigin.value)
if (!newOrigin) {
return;
}
const originOnly = new URL(newOrigin).origin
//See if origin is already in the list
if (!includes(origins.value, originOnly)) {
//Add to the list
origins.value.push(originOnly);
//If current tab was added, reload the tab
if (!origin) {
await tabs.reload(currentTab.value?.id)
}
}
}
const removeOrigin = async (origin?: string): Promise => {
//Allow undefined to remove current origin
const delOrigin = defaultTo(origin, currentOrigin.value)
if (!delOrigin) {
return;
}
//Get origin part of url
const delOriginOnly = new URL(delOrigin).origin
const allowList = get(origins)
//Remove the origin
origins.value = filter(allowList, (o) => !isEqual(o, delOriginOnly));
//If current tab was removed, reload the tab
if (!origin) {
await tabs.reload(currentTab.value?.id)
}
}
return {
waitForChange: waitForChangeFn([currentTab, enabled, origins]),
addOrigin: popupAndOptionsOnly(addOrigin),
removeOrigin: popupAndOptionsOnly(removeOrigin),
enable: popupAndOptionsOnly(async (value: boolean): Promise => {
set(enabled, value)
}),
async getStatus(): Promise {
return{
allowedOrigins: get(origins),
enabled: get(enabled),
currentOrigin: get(currentOrigin),
isAllowed: isOriginAllowed()
}
},
}
},
foreground: exportForegroundApi([
'addOrigin',
'removeOrigin',
'getStatus',
'enable',
'waitForChange'
])
}
}