aboutsummaryrefslogtreecommitdiff
path: root/extension/src/entries/background
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-09-06 13:51:13 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2023-09-06 13:51:13 -0400
commitcd8e865dad326f85ff2357ad90bbd6aa65dea68e (patch)
tree0d4a0bb8bafc4f807407e99c5e6bf4e1cb34217a /extension/src/entries/background
initial commit
Diffstat (limited to 'extension/src/entries/background')
-rw-r--r--extension/src/entries/background/auth-api.ts135
-rw-r--r--extension/src/entries/background/history.ts82
-rw-r--r--extension/src/entries/background/identity-api.ts61
-rw-r--r--extension/src/entries/background/main.ts105
-rw-r--r--extension/src/entries/background/nostr-api.ts124
-rw-r--r--extension/src/entries/background/permissions.ts80
-rw-r--r--extension/src/entries/background/script.js16
-rw-r--r--extension/src/entries/background/server-api/endpoints.ts72
-rw-r--r--extension/src/entries/background/server-api/index.ts80
-rw-r--r--extension/src/entries/background/serviceWorker.ts16
-rw-r--r--extension/src/entries/background/settings.ts125
-rw-r--r--extension/src/entries/background/types.ts76
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