aboutsummaryrefslogtreecommitdiff
path: root/extension/src/entries
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-11-22 15:07:08 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2023-11-22 15:07:08 -0500
commite272adcc3f32e31fe7668551453b8e34bc823c3e (patch)
tree680c695184ddbc27227578afa9f169d98a69f55a /extension/src/entries
parent2ba94602a87c87b47f566745bdab40ce75e0e879 (diff)
feature and internal api polish
Diffstat (limited to 'extension/src/entries')
-rw-r--r--extension/src/entries/contentScript/nostr-shim.js123
-rw-r--r--extension/src/entries/contentScript/primary/components/PromptPopup.vue10
-rw-r--r--extension/src/entries/contentScript/primary/main.js10
-rw-r--r--extension/src/entries/contentScript/util.ts138
-rw-r--r--extension/src/entries/nostr-provider.js6
-rw-r--r--extension/src/entries/options/components/Identities.vue9
-rw-r--r--extension/src/entries/options/components/SiteSettings.vue5
-rw-r--r--extension/src/entries/store/allowedOrigins.ts10
-rw-r--r--extension/src/entries/store/features.ts4
-rw-r--r--extension/src/entries/store/identity.ts4
10 files changed, 165 insertions, 154 deletions
diff --git a/extension/src/entries/contentScript/nostr-shim.js b/extension/src/entries/contentScript/nostr-shim.js
deleted file mode 100644
index 418b9c1..0000000
--- a/extension/src/entries/contentScript/nostr-shim.js
+++ /dev/null
@@ -1,123 +0,0 @@
-// 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 { isEqual, isNil, isEmpty } from 'lodash'
-import { apiCall } from '@vnuge/vnlib.browser'
-import { useScriptTag, watchOnce } from "@vueuse/core"
-import { useStore } from '../store'
-import { storeToRefs } from 'pinia'
-
-const _promptHandler = (() => {
- let _handler = undefined;
- return{
- invoke: (event) => _handler(event),
- set: (handler) => _handler = handler
- }
-})()
-
-export const usePrompt = (callback) => _promptHandler.set(callback);
-
-
-export const onLoad = async () =>{
-
- const store = useStore()
- const { nostr } = store.plugins
- const { isTabAllowed, selectedKey } = storeToRefs(store)
-
- const injectHandler = () => {
-
- //Setup listener for the content script to process nostr messages
- const ext = '@vnuge/nvault-extension'
-
- const scriptUrl = runtime.getURL('src/entries/nostr-provider.js')
-
- //setup script tag
- useScriptTag(scriptUrl, undefined, { manual: false, defer: true })
-
- //Only listen for messages if injection is enabled
- window.addEventListener('message', async ({ source, data, origin }) => {
-
- const invokePrompt = async (cb) => {
- //await propmt for user to allow the request
- const allow = await _promptHandler.invoke({ ...data, origin })
- //send request to background
- return response = allow ? await cb() : { error: 'User denied permission' }
- }
-
- //Confirm the message format is correct
- if (!isEqual(source, window) || isEmpty(data) || isNil(data.type)) {
- return
- }
- //Confirm extension is for us
- if (!isEqual(data.ext, ext)) {
- return
- }
-
- //clean any junk/methods with json parse/stringify
- data = JSON.parse(JSON.stringify(data))
-
- // pass on to background
- var response;
- await apiCall(async () => {
- switch (data.type) {
- case 'getPublicKey':
- return invokePrompt(async () => selectedKey.value.PublicKey)
- case 'signEvent':
- return invokePrompt(async () => {
- const event = data.payload.event
-
- //Set key id to selected key
- event.KeyId = selectedKey.value.Id
- event.pubkey = selectedKey.value.PublicKey;
-
- return await nostr.signEvent(event);
- })
- //Check the public key against selected key
- case 'getRelays':
- return invokePrompt(async () => await nostr.getRelays())
- case 'nip04.encrypt':
- return invokePrompt(async () => await nostr.nip04Encrypt({
- pubkey: data.payload.peer,
- content: data.payload.plaintext,
- //Set selected key id as our desired decryption key
- KeyId: selectedKey.value.Id
- }))
- case 'nip04.decrypt':
- return invokePrompt(async () => await nostr.nip04Decrypt({
- pubkey: data.payload.peer,
- content: data.payload.ciphertext,
- //Set selected key id as our desired decryption key
- KeyId: selectedKey.value.Id
- }))
- default:
- throw new Error('Unknown nostr message type')
- }
- })
- // return response message, must have the same id as the request
- window.postMessage({ ext, id: data.id, response }, origin);
- });
- }
-
- //Make sure the origin is allowed
- if (store.isTabAllowed === false){
- //If not allowed yet, wait for the store to update
- watchOnce(isTabAllowed, val => val ? injectHandler() : undefined);
- }
- else{
- injectHandler();
- }
-
-} \ No newline at end of file
diff --git a/extension/src/entries/contentScript/primary/components/PromptPopup.vue b/extension/src/entries/contentScript/primary/components/PromptPopup.vue
index b8b7cab..381f7b3 100644
--- a/extension/src/entries/contentScript/primary/components/PromptPopup.vue
+++ b/extension/src/entries/contentScript/primary/components/PromptPopup.vue
@@ -75,7 +75,7 @@
<script setup lang="ts">
import { ref } from 'vue'
-import { usePrompt } from '../../nostr-shim.js'
+import { usePrompt, type UserPermissionRequest } from '../../util'
import { computed } from 'vue';
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'
import { clone, first } from 'lodash';
@@ -88,11 +88,7 @@ const keyName = computed(() => selectedKey.value?.UserName)
const prompt = ref(null)
-interface PopupEvent{
- type: string
- msg: string
- origin: string
- data: any
+interface PopupEvent extends UserPermissionRequest {
allow: () => void
close: () => void
}
@@ -117,7 +113,7 @@ const allow = () => {
}
//Listen for events
-usePrompt(async (ev: PopupEvent) => {
+usePrompt((ev: UserPermissionRequest):Promise<boolean> => {
ev = clone(ev)
diff --git a/extension/src/entries/contentScript/primary/main.js b/extension/src/entries/contentScript/primary/main.js
index e73923d..dbfa07b 100644
--- a/extension/src/entries/contentScript/primary/main.js
+++ b/extension/src/entries/contentScript/primary/main.js
@@ -13,7 +13,7 @@
// 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 { createApp } from "vue";
import { createPinia } from 'pinia';
import { useBackgroundPiniaPlugin, identityPlugin, originPlugin } from '../../store'
@@ -25,7 +25,7 @@ import '@fontsource/noto-sans-masaram-gondi'
//We need inline styles to inject into the shadow dom
import tw from "~/assets/all.scss?inline";
import localStyle from './style.scss?inline'
-import { onLoad } from "../nostr-shim";
+import { onLoad } from "../util";
import { defer } from "lodash";
/* FONT AWESOME CONFIG */
@@ -35,6 +35,10 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
library.add(faCircleInfo)
+//The extension name, same as nostr-provider script path
+const ext = '@vnuge/nvault-extension'
+const scriptUrl = runtime.getURL('src/entries/nostr-provider.js')
+
renderContent([], (appRoot, shadowRoot) => {
//Create the background feature wiring
@@ -62,5 +66,5 @@ renderContent([], (appRoot, shadowRoot) => {
.mount(appRoot);
//Load the nostr shim
- defer(onLoad)
+ defer(() => onLoad(ext, scriptUrl))
}); \ No newline at end of file
diff --git a/extension/src/entries/contentScript/util.ts b/extension/src/entries/contentScript/util.ts
new file mode 100644
index 0000000..aa6aac3
--- /dev/null
+++ b/extension/src/entries/contentScript/util.ts
@@ -0,0 +1,138 @@
+// 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 { isEqual, isNil, isEmpty } from 'lodash'
+import { apiCall } from '@vnuge/vnlib.browser'
+import { Store, storeToRefs } from 'pinia'
+import { useScriptTag, watchOnce } from "@vueuse/core"
+import { useStore } from '../store'
+
+export type PromptHandler = (request: UserPermissionRequest) => Promise<boolean>
+
+export interface UserPermissionRequest {
+ type: string
+ msg: string
+ origin: string
+ data: any
+}
+
+const _promptHandler = (() => {
+ let _handler: PromptHandler | undefined = undefined;
+ return {
+ invoke(event:UserPermissionRequest){
+ if (!_handler) {
+ throw new Error('No prompt handler set')
+ }
+ return _handler(event)
+ },
+ set(handler: PromptHandler) {
+ _handler = handler
+ }
+ }
+})()
+
+
+const registerWindowHandler = (store: Store, extName: string) => {
+
+ const { selectedKey } = storeToRefs(store)
+ const { nostr } = store.plugins;
+
+ //Only listen for messages if injection is enabled
+ window.addEventListener('message', async ({ source, data, origin }) => {
+
+ //clean any junk/methods with json parse/stringify
+ data = JSON.parse(JSON.stringify(data))
+
+ const invokePrompt = async (cb:(...args:any) => Promise<any>) => {
+ //await propmt for user to allow the request
+ const allow = await _promptHandler.invoke({ ...data, origin })
+ //send request to background
+ return response = allow ? await cb() : { error: 'User denied permission' }
+ }
+
+ //Confirm the message format is correct
+ if (!isEqual(source, window) || isEmpty(data) || isNil(data.type)) {
+ return
+ }
+ //Confirm extension is for us
+ if (!isEqual(data.ext, extName)) {
+ return
+ }
+
+ // pass on to background
+ var response;
+ await apiCall(async () => {
+ switch (data.type) {
+ case 'getPublicKey':
+ return invokePrompt(async () => selectedKey.value?.PublicKey)
+ case 'signEvent':
+ return invokePrompt(async () => {
+ const event = data.payload.event
+
+ //Set key id to selected key
+ event.KeyId = selectedKey.value!.Id
+ event.pubkey = selectedKey.value!.PublicKey;
+
+ return await nostr.signEvent(event);
+ })
+ //Check the public key against selected key
+ case 'getRelays':
+ return invokePrompt(async () => await nostr.getRelays())
+ case 'nip04.encrypt':
+ return invokePrompt(async () => await nostr.nip04Encrypt({
+ pubkey: data.payload.peer,
+ content: data.payload.plaintext,
+ //Set selected key id as our desired decryption key
+ KeyId: selectedKey.value!.Id
+ }))
+ case 'nip04.decrypt':
+ return invokePrompt(async () => await nostr.nip04Decrypt({
+ pubkey: data.payload.peer,
+ content: data.payload.ciphertext,
+ //Set selected key id as our desired decryption key
+ KeyId: selectedKey.value!.Id
+ }))
+ default:
+ throw new Error('Unknown nostr message type')
+ }
+ })
+ // return response message, must have the same id as the request
+ window.postMessage({ ext: extName, id: data.id, response }, origin);
+ });
+}
+
+export const usePrompt = (callback: PromptHandler) => _promptHandler.set(callback);
+
+export const onLoad = async (extName: string, scriptUrl: string) => {
+
+ const store = useStore()
+ const { isTabAllowed } = storeToRefs(store)
+
+ const injectHandler = () => {
+ //inject the nostr provider script into the page
+ useScriptTag(scriptUrl, undefined, { manual: false, defer: true })
+ //Regsiter listener for messages from the injected script
+ registerWindowHandler(store, extName)
+ }
+
+ //Make sure the origin is allowed
+ if (isTabAllowed.value === false) {
+ //If not allowed yet, wait for the store to update
+ watchOnce(isTabAllowed, val => val ? injectHandler() : undefined);
+ }
+ else {
+ injectHandler();
+ }
+} \ No newline at end of file
diff --git a/extension/src/entries/nostr-provider.js b/extension/src/entries/nostr-provider.js
index 9fa3bb7..7d8f3c5 100644
--- a/extension/src/entries/nostr-provider.js
+++ b/extension/src/entries/nostr-provider.js
@@ -74,9 +74,9 @@ window.nostr = {
} ,
async signEvent(event){
- const { event:ev } = await sendMessage('signEvent', { event })
- debugLog("Signed event", ev);
- return ev
+ const signed = await sendMessage('signEvent', { event })
+ debugLog("Signed event", signed);
+ return signed
},
getRelays(){
diff --git a/extension/src/entries/options/components/Identities.vue b/extension/src/entries/options/components/Identities.vue
index e3af74e..0e3e79d 100644
--- a/extension/src/entries/options/components/Identities.vue
+++ b/extension/src/entries/options/components/Identities.vue
@@ -158,11 +158,10 @@ const onNip05Download = () => {
//create file blob
const blob = new Blob([JSON.stringify({ names:nip05 })], { type: 'application/json' })
- //Download the file
- downloadAnchor.value!.href = URL.createObjectURL(blob);
- downloadAnchor.value?.setAttribute('download', 'nostr.json')
- downloadAnchor.value?.click();
-
+ const anchor = get(downloadAnchor);
+ anchor!.href = URL.createObjectURL(blob);
+ anchor!.setAttribute('download', 'nip05.json')
+ anchor!.click();
})
}
diff --git a/extension/src/entries/options/components/SiteSettings.vue b/extension/src/entries/options/components/SiteSettings.vue
index c0c2e93..b31bb9c 100644
--- a/extension/src/entries/options/components/SiteSettings.vue
+++ b/extension/src/entries/options/components/SiteSettings.vue
@@ -7,7 +7,7 @@
Extension settings
</h3>
<div class="my-6">
- <fieldset :disabled="waiting.value">
+ <fieldset :disabled="waiting">
<div class="">
<div class="w-full">
<div class="flex flex-row w-full">
@@ -139,7 +139,7 @@ const { apply, data, buffer, modified, update } = useDataBuffer(settings.value,
})
//Watch for store settings changes and apply them
-watch(settings, v => apply(v.value))
+watch(settings, v => apply(v))
const originProtection = computed({
get: () => isOriginProtectionOn.value,
@@ -196,7 +196,6 @@ const onSave = async () => {
toggleEdit();
}
-
const testConnection = async () =>{
return await apiCall(async ({axios, toaster}) =>{
try{
diff --git a/extension/src/entries/store/allowedOrigins.ts b/extension/src/entries/store/allowedOrigins.ts
index d6de42d..661dd43 100644
--- a/extension/src/entries/store/allowedOrigins.ts
+++ b/extension/src/entries/store/allowedOrigins.ts
@@ -3,15 +3,15 @@ import 'pinia'
import { } from 'lodash'
import { PiniaPluginContext } from 'pinia'
import { computed, shallowRef } from 'vue';
-import { onWatchableChange } from '../../features/types';
+import { onWatchableChange } from '../../features';
import { type AllowedOriginStatus } from '../../features/nip07allow-api';
declare module 'pinia' {
export interface PiniaCustomProperties {
- isTabAllowed: boolean;
- currentOrigin: string | undefined;
- allowedOrigins: Array<string>;
- isOriginProtectionOn: boolean;
+ readonly isTabAllowed: boolean;
+ readonly currentOrigin: string | undefined;
+ readonly allowedOrigins: Array<string>;
+ readonly isOriginProtectionOn: boolean;
allowOrigin(origin?:string): Promise<void>;
dissallowOrigin(origin?:string): Promise<void>;
disableOriginProtection(): Promise<void>;
diff --git a/extension/src/entries/store/features.ts b/extension/src/entries/store/features.ts
index 219386f..c619b0e 100644
--- a/extension/src/entries/store/features.ts
+++ b/extension/src/entries/store/features.ts
@@ -13,10 +13,10 @@ import {
useSettingsApi,
useForegoundFeatures,
useEventTagFilterApi,
- useInjectAllowList
+ useInjectAllowList,
+ onWatchableChange
} from "../../features"
-import { onWatchableChange } from '../../features/types'
import { ChannelContext } from '../../messaging'
export type BgPlugins = ReturnType<typeof usePlugins>
diff --git a/extension/src/entries/store/identity.ts b/extension/src/entries/store/identity.ts
index 320263f..b1635f2 100644
--- a/extension/src/entries/store/identity.ts
+++ b/extension/src/entries/store/identity.ts
@@ -2,9 +2,8 @@
import 'pinia'
import { } from 'lodash'
import { PiniaPluginContext } from 'pinia'
-import { NostrPubKey } from '../../features'
+import { onWatchableChange, type NostrPubKey } from '../../features'
import { shallowRef } from 'vue';
-import { onWatchableChange } from '../../features/types';
declare module 'pinia' {
export interface PiniaCustomStateProperties {
@@ -17,7 +16,6 @@ declare module 'pinia' {
}
}
-
export const identityPlugin = ({ store }: PiniaPluginContext) => {
const { identity } = store.plugins