diff options
author | vnugent <public@vaughnnugent.com> | 2023-09-06 13:51:13 -0400 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2023-09-06 13:51:13 -0400 |
commit | cd8e865dad326f85ff2357ad90bbd6aa65dea68e (patch) | |
tree | 0d4a0bb8bafc4f807407e99c5e6bf4e1cb34217a /extension/src/entries/background |
initial commit
Diffstat (limited to 'extension/src/entries/background')
-rw-r--r-- | extension/src/entries/background/auth-api.ts | 135 | ||||
-rw-r--r-- | extension/src/entries/background/history.ts | 82 | ||||
-rw-r--r-- | extension/src/entries/background/identity-api.ts | 61 | ||||
-rw-r--r-- | extension/src/entries/background/main.ts | 105 | ||||
-rw-r--r-- | extension/src/entries/background/nostr-api.ts | 124 | ||||
-rw-r--r-- | extension/src/entries/background/permissions.ts | 80 | ||||
-rw-r--r-- | extension/src/entries/background/script.js | 16 | ||||
-rw-r--r-- | extension/src/entries/background/server-api/endpoints.ts | 72 | ||||
-rw-r--r-- | extension/src/entries/background/server-api/index.ts | 80 | ||||
-rw-r--r-- | extension/src/entries/background/serviceWorker.ts | 16 | ||||
-rw-r--r-- | extension/src/entries/background/settings.ts | 125 | ||||
-rw-r--r-- | extension/src/entries/background/types.ts | 76 |
12 files changed, 972 insertions, 0 deletions
diff --git a/extension/src/entries/background/auth-api.ts b/extension/src/entries/background/auth-api.ts new file mode 100644 index 0000000..ed18b5b --- /dev/null +++ b/extension/src/entries/background/auth-api.ts @@ -0,0 +1,135 @@ +// 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 <https://www.gnu.org/licenses/>. + +import { debugLog, useAxios, usePkiAuth, useSession, useSessionUtils, useUser } from "@vnuge/vnlib.browser"; +import { AxiosInstance } from "axios"; +import { runtime } from "webextension-polyfill"; +import { BridgeMessage } from "webext-bridge"; +import { useSettings } from "./settings"; +import { JsonObject } from "type-fest"; +import { ClientStatus, LoginMessage } from "./types"; + +interface ApiHandle { + axios: AxiosInstance +} + +export interface ProectedHandler<T extends JsonObject> { + (message: BridgeMessage<T>): Promise<any> +} + +export const useAuthApi = (() => { + + const { loggedIn } = useSession(); + const { clearLoginState } = useSessionUtils(); + const { logout, getProfile, heartbeat, userName } = useUser(); + const { currentConfig } = useSettings(); + + const apiCall = async <T>(asyncFunc: (h: ApiHandle) => Promise<T>): Promise<T> => { + try { + //Get configured axios instance from vnlib + const axios = useAxios(null); + + //Exec the async function + return await asyncFunc({ axios }) + } catch (errMsg) { + debugLog(errMsg) + // See if the error has an axios response + throw { ...errMsg }; + } + } + + const protect = <T extends JsonObject>(cbHandler: ProectedHandler<T>) =>{ + return (message: BridgeMessage<T>) : Promise<any> => { + if (message.sender.context === 'options' || message.sender.context === 'popup') { + return cbHandler(message) + } + throw new Error('Unauthorized') + } + } + + const onLogin = protect(async ({data} : BridgeMessage<LoginMessage>): Promise<any> => { + + //Perform login + return await apiCall(async ({ axios }) => { + const { login } = usePkiAuth(`${currentConfig.value.accountBasePath}/pki`); + await login(data.token) + return true; + }) + }) + + const onLogout = protect(async () : Promise<void> => { + return await apiCall(async () => { + await logout() + //Cleanup after logout + clearLoginState() + }) + }) + + const onGetProfile = protect(async () : Promise<any> => { + return await apiCall(async () => await getProfile()) + }) + + const onGetStatus = async (): Promise<ClientStatus> => { + return { + //Logged in if the cookie is set and the api flag is set + loggedIn: loggedIn.value, + //username + userName: userName.value, + //dark mode flag + darkMode: currentConfig.value.darkMode + } + } + + //We can send post messages to the server heartbeat endpoint to get status + const runHeartbeat = async () => { + //Only run if the api thinks its logged in, and config is enabled + if (!loggedIn.value || currentConfig.value.heartbeat !== true) { + return + } + + try { + //Post against the heartbeat endpoint + await heartbeat() + } + catch (error) { + if (error.response?.status === 401 || error.response?.status === 403) { + //If we get a 401, the user is no longer logged in + clearLoginState() + } + } + } + + //Setup autoheartbeat + runtime.onInstalled.addListener(async () => { + //Configure interval to run every 5 minutes to update the status + setInterval(runHeartbeat, 60 * 1000); + + //Run immediately + runHeartbeat(); + }); + + return () => { + return{ + loggedIn, + apiCall, + protect, + userName, + onLogin, + onLogout, + onGetProfile, + onGetStatus + } + } +})()
\ No newline at end of file diff --git a/extension/src/entries/background/history.ts b/extension/src/entries/background/history.ts new file mode 100644 index 0000000..b3f3733 --- /dev/null +++ b/extension/src/entries/background/history.ts @@ -0,0 +1,82 @@ +// 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 <https://www.gnu.org/licenses/>. + +import { runtime, storage } from "webextension-polyfill"; +import { useSettings } from "./settings"; +import { isEqual, remove } from "lodash"; +import { ref } from "vue"; + +const evHistory = ref([]); + +export interface HistoryEvent extends Object{ + +} + +export const useHistory = (() => { + const { currentConfig } = useSettings(); + + const pushEvent = (event: HistoryEvent) => { + + //Limit the history to 50 events + if (evHistory.value.length > currentConfig.value.maxHistory) { + evHistory.value.shift(); + } + + evHistory.value.push(event); + + //Save the history but dont wait for it + storage.local.set({ eventHistory: evHistory }); + } + + const getHistory = (): HistoryEvent[] => { + return [...evHistory.value]; + } + + const clearHistory = () => { + evHistory.value.length = 0; + storage.local.set({ eventHistory: evHistory }); + } + + const removeItem = (event: HistoryEvent) => { + //Remove the event from the history + remove(evHistory.value, (ev) => isEqual(ev, event)); + //Save the history but dont wait for it + storage.local.set({ eventHistory: evHistory }); + } + + const onStartup = async () => { + //Recover the history array + const { eventHistory } = await storage.local.get('eventHistory'); + + //Push the history into the array + evHistory.value.push(...eventHistory); + } + + //Reload the history on startup + runtime.onStartup.addListener(onStartup); + runtime.onInstalled.addListener(onStartup); + + return () =>{ + return { + pushEvent, + getHistory, + clearHistory, + removeItem + } + } +})() + + +//Listen for messages
\ No newline at end of file diff --git a/extension/src/entries/background/identity-api.ts b/extension/src/entries/background/identity-api.ts new file mode 100644 index 0000000..612f36e --- /dev/null +++ b/extension/src/entries/background/identity-api.ts @@ -0,0 +1,61 @@ +// 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 <https://www.gnu.org/licenses/>. + +import { useAuthApi } from "./auth-api"; +import { useSettings } from "./settings"; + +export const useIdentityApi = (() => { + + const { apiCall, protect } = useAuthApi(); + const { currentConfig } = useSettings(); + + const onCreateIdentity = protect(async ({data}) => { + //Create a new identity + return await apiCall(async ({ axios }) => { + const response = await axios.put(`${currentConfig.value.nostrEndpoint}?type=identity`, data) + + if (response.data.success) { + return response.data.result; + } + //If we get here, the login failed + throw { response } + }) + }) + + const onUpdateIdentity = protect(async ({data}) => { + return await apiCall(async ({ axios }) => { + + delete data.Created; + delete data.LastModified; + + //Create a new identity + const response = await axios.patch(`${currentConfig.value.nostrEndpoint}?type=identity`, data) + + if (response.data.success) { + return response.data.result; + } + //If we get here, the login failed + throw { response } + }) + }) + + return () =>{ + return{ + onCreateIdentity, + onUpdateIdentity + } + } + +})()
\ No newline at end of file diff --git a/extension/src/entries/background/main.ts b/extension/src/entries/background/main.ts new file mode 100644 index 0000000..b4080d6 --- /dev/null +++ b/extension/src/entries/background/main.ts @@ -0,0 +1,105 @@ +// 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 <https://www.gnu.org/licenses/>. + +import { runtime } from "webextension-polyfill"; +import { HistoryEvent, useHistory } from "./history"; +import { useNostrApi } from "./nostr-api"; +import { useIdentityApi } from "./identity-api"; +import { useSettings } from "./settings"; +import { onMessage } from "webext-bridge/background"; +import { useAuthApi } from "./auth-api"; +import { JsonObject } from "type-fest"; + +//Init the history api +useHistory(); + +runtime.onInstalled.addListener(() => { + console.info("Extension installed successfully"); +}); + + +//Register settings handlers +const { onGetSiteConfig, onSetSitConfig } = useSettings(); + +onMessage('getSiteConfig', onGetSiteConfig); +onMessage('setSiteConfig', onSetSitConfig); + +//Register the api handlers +const { onGetProfile, onGetStatus, onLogin, onLogout, protect } = useAuthApi(); + +onMessage('getProfile', onGetProfile); +onMessage('getStatus', onGetStatus); +onMessage('login', onLogin); +onMessage('logout', onLogout); + +//Register the identity handlers +const { onCreateIdentity, onUpdateIdentity } = useIdentityApi(); + +onMessage('createIdentity', onCreateIdentity); +onMessage('updateIdentity', onUpdateIdentity); + +//Register the nostr handlers +const { + onGetPubKey, + onSelectKey, + onSignEvent, + onGetAllKeys, + onGetRelays, + onNip04Decrypt, + onNip04Encrypt, + onDeleteKey, + onSetRelay +} = useNostrApi(); + +onMessage('getPublicKey', onGetPubKey); +onMessage('selectKey', onSelectKey); +onMessage('signEvent', onSignEvent); +onMessage('getAllKeys', onGetAllKeys); +onMessage('getRelays', onGetRelays); +onMessage('setRelay', onSetRelay); +onMessage('deleteKey', onDeleteKey); +onMessage('nip04.decrypt', onNip04Decrypt); +onMessage('nip04.encrypt', onNip04Encrypt); + +//Use history api +const { getHistory, clearHistory, removeItem, pushEvent } = useHistory(); + +enum HistoryType { + get = 'get', + clear = 'clear', + remove = 'remove', + push = 'push' +} + +interface HistoryMessage extends JsonObject { + action: HistoryType, + event: string +} + +onMessage <HistoryMessage>('history', protect(async ({data}) =>{ + switch(data.action){ + case HistoryType.get: + return getHistory(); + case HistoryType.clear: + clearHistory(); + break; + case HistoryType.remove: + removeItem(data.event); + break; + case HistoryType.push: + pushEvent(data.event); + break; + } +}))
\ No newline at end of file diff --git a/extension/src/entries/background/nostr-api.ts b/extension/src/entries/background/nostr-api.ts new file mode 100644 index 0000000..fb9130b --- /dev/null +++ b/extension/src/entries/background/nostr-api.ts @@ -0,0 +1,124 @@ +// 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 <https://www.gnu.org/licenses/>. + +import { useSettings } from "./settings"; +import { useAuthApi } from "./auth-api"; +import { computed, ref, watch } from "vue"; + +import { find, isArray } from "lodash"; +import { BridgeMessage } from "webext-bridge"; +import { NostrRelay, NostrPubKey, EventMessage, NostrEvent } from './types' +import { Endpoints, initApi } from "./server-api"; + +export const useNostrApi = (() => { + + const { currentConfig } = useSettings(); + const { apiCall, protect, loggedIn } = useAuthApi(); + + const nostrUrl = computed(() => currentConfig.value.nostrEndpoint || '/nostr') + + //Init the api endpooints + const { execRequest } = initApi(nostrUrl); + + //Get the current selected key + const selectedKey = ref<NostrPubKey | null>({} as NostrPubKey) + + const onGetPubKey = () => { + //Selected key is allowed from content script + return { ...selectedKey.value } + } + + const onDeleteKey = protect<NostrPubKey>(({ data }) => apiCall(() => execRequest<NostrPubKey>(Endpoints.DeleteKey, data))) + + const onSelectKey = protect<NostrPubKey>(async ({ data }) => { + //Set the selected key to the value + selectedKey.value = data + }) + + const onGetAllKeys = protect(async () => { + return await apiCall(async () => { + + //Get the keys from the server + const data = await execRequest<NostrPubKey[]>(Endpoints.GetKeys); + + //Response must be an array of key objects + if (!isArray(data)) { + return []; + } + + //Make sure the selected keyid is in the list, otherwise unselect the key + if (data?.length > 0) { + if (!find(data, k => k.Id === selectedKey.value?.Id)) { + selectedKey.value = null; + } + } + + return [ ...data ] + }) + }) + + //Unprotect this handler so it can be called from the content script + const onSignEvent = (async ({ data }: BridgeMessage<EventMessage>) => { + //Set the key id from our current selection + data.event.KeyId = selectedKey.value?.Id || ''; //Pass key selection error to server + + //Sign the event + return await apiCall(async () => { + //Sign the event + const event = await execRequest<NostrEvent>(Endpoints.SignEvent, data.event); + return { event }; + }) + }) + + const onGetRelays = async () => { + return await apiCall(async () => { + //Get preferred relays for the current user + const data = await execRequest<NostrRelay[]>(Endpoints.GetRelays) + return [ ...data ] + }) + } + + + const onSetRelay = protect<NostrRelay>(({ data }) => apiCall(() => execRequest<NostrRelay>(Endpoints.SetRelay, data))); + + const onNip04Encrypt = protect(async ({ data }) => { + console.log('nip04.encrypt', data) + return { ciphertext: 'ciphertext' } + }) + + const onNip04Decrypt = protect(async ({ data }) => { + console.log('nip04.decrypt', data) + return { plaintext: 'plaintext' } + }) + + //Clear the selected key if the user logs out + watch(loggedIn, (li) => li ? null : selectedKey.value = null) + + return () => { + return{ + selectedKey, + nostrUrl, + onGetPubKey, + onSelectKey, + onGetAllKeys, + onSignEvent, + onGetRelays, + onSetRelay, + onNip04Encrypt, + onNip04Decrypt, + onDeleteKey + } + } +})()
\ No newline at end of file diff --git a/extension/src/entries/background/permissions.ts b/extension/src/entries/background/permissions.ts new file mode 100644 index 0000000..f12c84c --- /dev/null +++ b/extension/src/entries/background/permissions.ts @@ -0,0 +1,80 @@ +// 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 <https://www.gnu.org/licenses/>. + +import { useStorageAsync } from "@vueuse/core"; +import { find, isEmpty, remove } from "lodash"; +import { storage } from "webextension-polyfill"; +import { useAuthApi } from "./auth-api"; +import { useSettings } from "./settings"; + +const permissions = useStorageAsync("permissions", [], storage.local); + +export const setAutoAllow = async (origin, mKind, keyId) => { + permissions.value.push({ origin, mKind, keyId, }) +} + +/** + * Determines if the user has previously allowed the origin to use the key to sign events + * of the desired kind + * @param {*} origin The site origin requesting the permission + * @param {*} mKind The kind of message being signed + * @param {*} keyId The keyId of the key being used to sign the message + */ +export const isAutoAllow = async (origin, mKind, keyId) => { + return find(permissions.value, p => p.origin === origin && p.mKind === mKind && p.keyId === keyId) !== undefined +} + +/** + * Removes the auto allow permission from the list + * @param {*} origin The site origin requesting the permission + * @param {*} mKind The message kind being signed + * @param {*} keyId The keyId of the key being used to sign the message + */ +export const removeAutoAllow = async (origin, mKind, keyId) => { + //Remove the permission from the list + remove(permissions.value, p => p.origin === origin && p.mKind === mKind && p.keyId === keyId); +} + + +export const useSitePermissions = (() => { + + const { apiCall, protect } = useAuthApi(); + const { currentConfig } = useSettings(); + + + const getCurrentPerms = async () => { + const { permissions } = await storage.local.get('permissions'); + + //Store a default config if none exists + if (isEmpty(permissions)) { + await storage.local.set({ siteConfig: defaultConfig }); + } + + //Merge the default config with the site config + return merge(defaultConfig, siteConfig) + } + + const onIsSiteEnabled = protect(async ({ data }) => { + + }) + + return () => { + return { + onCreateIdentity, + onUpdateIdentity + } + } + +})()
\ No newline at end of file diff --git a/extension/src/entries/background/script.js b/extension/src/entries/background/script.js new file mode 100644 index 0000000..b0211d1 --- /dev/null +++ b/extension/src/entries/background/script.js @@ -0,0 +1,16 @@ +// 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 <https://www.gnu.org/licenses/>. + +import "./main.ts"; diff --git a/extension/src/entries/background/server-api/endpoints.ts b/extension/src/entries/background/server-api/endpoints.ts new file mode 100644 index 0000000..a7f1488 --- /dev/null +++ b/extension/src/entries/background/server-api/endpoints.ts @@ -0,0 +1,72 @@ +// 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 <https://www.gnu.org/licenses/>. + + +import { useAxios } from "@vnuge/vnlib.browser"; +import { Method } from "axios"; + +export interface EndpointDefinition { + readonly method: Method + path(request?: any): string + onRequest: (request?: any) => Promise<any> + onResponse: (response: any, request?: any) => Promise<any> +} + +export interface EndpointDefinitionReg<T extends string> extends EndpointDefinition { + readonly id: T +} + +export const initEndponts = () => { + + const endpoints = new Map<string, EndpointDefinition>(); + + const registerEndpoint = <T extends string>(def: EndpointDefinitionReg<T>) => { + //Store the handler by its id + endpoints.set(def.id, def); + return def; + } + + const getEndpoint = <T extends string>(id: T): EndpointDefinition | undefined => { + return endpoints.get(id); + } + + const execRequest = async <T>(id: string, request?: any): Promise<T> => { + const endpoint = getEndpoint(id); + if (!endpoint) { + throw new Error(`Endpoint ${id} not found`); + } + + //Compute the path from the request + const path = endpoint.path(request); + + //Execute the request handler + const req = await endpoint.onRequest(request); + + //Get axios + const axios = useAxios(null); + + //Exec the request + const { data } = await axios({ method: endpoint.method, url: path, data: req }); + + //exec the response handler and return its result + return await endpoint.onResponse(data, request); + } + + return { + registerEndpoint, + getEndpoint, + execRequest + } +} diff --git a/extension/src/entries/background/server-api/index.ts b/extension/src/entries/background/server-api/index.ts new file mode 100644 index 0000000..3e1ada0 --- /dev/null +++ b/extension/src/entries/background/server-api/index.ts @@ -0,0 +1,80 @@ +// 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 <https://www.gnu.org/licenses/>. + + +import { Ref } from "vue" +import { WebMessage } from "@vnuge/vnlib.browser" +import { initEndponts } from "./endpoints" +import { NostrEvent, NostrPubKey, NostrRelay } from "../types" + +export enum Endpoints { + GetKeys = 'getKeys', + DeleteKey = 'deleteKey', + SignEvent = 'signEvent', + GetRelays = 'getRelays', + SetRelay = 'setRelay', + Encrypt = 'encrypt', + Decrypt = 'decrypt', +} + +export const initApi = (nostrUrl: Ref<string>) => { + const { registerEndpoint, execRequest } = initEndponts() + + registerEndpoint({ + id: Endpoints.GetKeys, + method: 'GET', + path: () => `${nostrUrl.value}?type=getKeys`, + onRequest: () => Promise.resolve(), + onResponse: (response) => Promise.resolve(response) + }) + + registerEndpoint({ + id: Endpoints.DeleteKey, + method: 'DELETE', + path: (key: NostrPubKey) => `${nostrUrl.value}?type=identity&key_id=${key.Id}`, + onRequest: () => Promise.resolve(), + onResponse: (response: WebMessage) => response.getResultOrThrow() + }) + + registerEndpoint({ + id: Endpoints.SignEvent, + method: 'POST', + path: () => `${nostrUrl.value}?type=signEvent`, + onRequest: (event: NostrEvent) => Promise.resolve(event), + onResponse: async (response: WebMessage<NostrEvent>) => { + return response.getResultOrThrow() + } + }) + + registerEndpoint({ + id: Endpoints.GetRelays, + method: 'GET', + path: () => `${nostrUrl.value}?type=getRelays`, + onRequest: () => Promise.resolve(), + onResponse: (response) => Promise.resolve(response) + }) + + registerEndpoint({ + id: Endpoints.SetRelay, + method: 'POST', + path: () => `${nostrUrl.value}?type=relay`, + onRequest: (relay: NostrRelay) => Promise.resolve(relay), + onResponse: (response) => Promise.resolve(response) + }) + + return { + execRequest + } +}
\ No newline at end of file diff --git a/extension/src/entries/background/serviceWorker.ts b/extension/src/entries/background/serviceWorker.ts new file mode 100644 index 0000000..cb6f42a --- /dev/null +++ b/extension/src/entries/background/serviceWorker.ts @@ -0,0 +1,16 @@ +// 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 <https://www.gnu.org/licenses/>. + +import "./main"; diff --git a/extension/src/entries/background/settings.ts b/extension/src/entries/background/settings.ts new file mode 100644 index 0000000..98d6aa6 --- /dev/null +++ b/extension/src/entries/background/settings.ts @@ -0,0 +1,125 @@ +// 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 <https://www.gnu.org/licenses/>. + +import { runtime, storage } from "webextension-polyfill" +import { isEmpty, isEqual, merge } from 'lodash' +import { configureApi, debugLog } from '@vnuge/vnlib.browser' +import { readonly, ref } from "vue"; +import { BridgeMessage } from "webext-bridge"; +import { JsonObject } from "type-fest"; + +export interface PluginConfig extends JsonObject { + readonly apiUrl: string; + readonly accountBasePath: string; + readonly nostrEndpoint: string; + readonly heartbeat: boolean; + readonly maxHistory: number; + readonly darkMode: boolean; + readonly autoInject: boolean; +} + +//Default storage config +const defaultConfig : PluginConfig = { + apiUrl: import.meta.env.VITE_API_URL, + accountBasePath: import.meta.env.VITE_ACCOUNTS_BASE_PATH, + nostrEndpoint: import.meta.env.VITE_NOSTR_ENDPOINT, + heartbeat: import.meta.env.VITE_HEARTBEAT_ENABLED === 'true', + maxHistory: 50, + darkMode: false, + autoInject: true +}; + +export const useSettings = (() =>{ + + const currentConfig = ref<PluginConfig>({} as PluginConfig); + + const getCurrentConfig = async () => { + const { siteConfig } = await storage.local.get('siteConfig'); + + //Store a default config if none exists + if(isEmpty(siteConfig)){ + await storage.local.set({ siteConfig: defaultConfig }); + } + + //Merge the default config with the site config + return merge(defaultConfig, siteConfig) + } + + const restoreApiSettings = async () => { + //Set the current config + currentConfig.value = await getCurrentConfig();; + + //Configure the vnlib api + configureApi({ + session: { + cookiesEnabled: false, + bidSize: 32, + storage: localStorage + }, + user: { + accountBasePath: currentConfig.value.accountBasePath, + storage: localStorage, + }, + axios: { + baseURL: currentConfig.value.apiUrl, + tokenHeader: import.meta.env.VITE_WEB_TOKEN_HEADER, + } + }) + } + + const saveConfig = async (config: PluginConfig) : Promise<void> => { + await storage.local.set({ siteConfig: config }); + } + + const onGetSiteConfig = async ({ } :BridgeMessage<any>): Promise<PluginConfig> => { + return { ...currentConfig.value } + } + + const onSetSitConfig = async ({ sender, data }: BridgeMessage<PluginConfig>) : Promise<void> => { + //Config messages should only come from the options page + if (sender.context !== 'options') { + throw new Error('Unauthorized'); + } + + //Save the config + await saveConfig(data); + + //Restore the api settings + restoreApiSettings(); + + debugLog('Config settings saved!'); + } + + runtime.onInstalled.addListener(() => { + restoreApiSettings(); + debugLog('Server settings restored from storage'); + }); + + runtime.onConnect.addListener(async () => { + //refresh the config on connect + currentConfig.value = await getCurrentConfig(); + }) + + return () =>{ + return{ + getCurrentConfig, + restoreApiSettings, + saveConfig, + currentConfig:readonly(currentConfig), + onGetSiteConfig, + onSetSitConfig + } + } +})()
\ No newline at end of file diff --git a/extension/src/entries/background/types.ts b/extension/src/entries/background/types.ts new file mode 100644 index 0000000..d459ea1 --- /dev/null +++ b/extension/src/entries/background/types.ts @@ -0,0 +1,76 @@ +// 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 <https://www.gnu.org/licenses/>. + +import { JsonObject } from "type-fest"; + +export interface NostrPubKey extends JsonObject { + readonly Id: string, + readonly UserName: string, + readonly PublicKey: string, + readonly Created: string, + readonly LastModified: string +} + +export interface NostrEvent extends JsonObject { + KeyId: string, + readonly id: string, + readonly pubkey: string, + readonly content: string, +} + +export interface EventMessage extends JsonObject { + readonly event: NostrEvent +} + +export interface NostrRelay extends JsonObject { + readonly Id: string, + readonly url: string, + readonly flags: number, + readonly Created: string, + readonly LastModified: string +} + +export interface LoginMessage extends JsonObject { + readonly token: string +} + +export interface ClientStatus { + readonly loggedIn: boolean; + readonly userName: string | null; + readonly darkMode: boolean; +} + +export enum NostrRelayFlags { + None = 0, + Default = 1, + Preferred = 2, +} + +export enum NostrRelayMessageType{ + updateRelay = 1, + addRelay = 2, + deleteRelay = 3 +} + +export interface NostrRelayMessage extends JsonObject { + readonly relay: NostrRelay + readonly type: NostrRelayMessageType +} + +export interface LoginMessage extends JsonObject { + readonly token: string + readonly username: string + readonly password: string +}
\ No newline at end of file |