mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-29 00:10:52 +00:00
Add amber support
This commit is contained in:
parent
223a1550f1
commit
d87e8ca363
@ -79,7 +79,7 @@
|
|||||||
</Anchor>
|
</Anchor>
|
||||||
<MenuDesktopItem path="/notes" isActive={isFeedPage || isListPage}>Feeds</MenuDesktopItem>
|
<MenuDesktopItem path="/notes" isActive={isFeedPage || isListPage}>Feeds</MenuDesktopItem>
|
||||||
{#if !$env.FORCE_GROUP && $env.PLATFORM_RELAYS.length === 0}
|
{#if !$env.FORCE_GROUP && $env.PLATFORM_RELAYS.length === 0}
|
||||||
<MenuDesktopItem path="/settings/relays" isActive={$page.path.startsWith("/settings/relays")}>
|
<MenuDesktopItem path="/settings/relays" isActive={$page?.path.startsWith("/settings/relays")}>
|
||||||
<div class="relative inline-block">
|
<div class="relative inline-block">
|
||||||
Relays
|
Relays
|
||||||
{#if $slowConnections.length > 0}
|
{#if $slowConnections.length > 0}
|
||||||
@ -91,7 +91,7 @@
|
|||||||
<MenuDesktopItem
|
<MenuDesktopItem
|
||||||
path="/notifications"
|
path="/notifications"
|
||||||
disabled={!$canSign}
|
disabled={!$canSign}
|
||||||
isActive={$page.path.startsWith("/notifications")}>
|
isActive={$page?.path.startsWith("/notifications")}>
|
||||||
<div class="relative inline-block">
|
<div class="relative inline-block">
|
||||||
Notifications
|
Notifications
|
||||||
{#if $hasNewNotifications}
|
{#if $hasNewNotifications}
|
||||||
@ -102,7 +102,7 @@
|
|||||||
<MenuDesktopItem
|
<MenuDesktopItem
|
||||||
path="/channels"
|
path="/channels"
|
||||||
disabled={!$canSign}
|
disabled={!$canSign}
|
||||||
isActive={$page.path.startsWith("/channels")}>
|
isActive={$page?.path.startsWith("/channels")}>
|
||||||
<div class="relative inline-block">
|
<div class="relative inline-block">
|
||||||
Messages
|
Messages
|
||||||
{#if $hasNewMessages}
|
{#if $hasNewMessages}
|
||||||
@ -110,14 +110,14 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</MenuDesktopItem>
|
</MenuDesktopItem>
|
||||||
<MenuDesktopItem path="/events" isActive={$page.path.startsWith("/events")}
|
<MenuDesktopItem path="/events" isActive={$page?.path.startsWith("/events")}
|
||||||
>Calendar</MenuDesktopItem>
|
>Calendar</MenuDesktopItem>
|
||||||
{#if $env.ENABLE_MARKET}
|
{#if $env.ENABLE_MARKET}
|
||||||
<MenuDesktopItem path="/listings" isActive={$page.path.startsWith("/listings")}
|
<MenuDesktopItem path="/listings" isActive={$page?.path.startsWith("/listings")}
|
||||||
>Market</MenuDesktopItem>
|
>Market</MenuDesktopItem>
|
||||||
{/if}
|
{/if}
|
||||||
{#if !$env.FORCE_GROUP}
|
{#if !$env.FORCE_GROUP}
|
||||||
<MenuDesktopItem path="/groups" isActive={$page.path.startsWith("/groups")}
|
<MenuDesktopItem path="/groups" isActive={$page?.path.startsWith("/groups")}
|
||||||
>Groups</MenuDesktopItem>
|
>Groups</MenuDesktopItem>
|
||||||
{/if}
|
{/if}
|
||||||
<FlexColumn small class="absolute bottom-0 w-72">
|
<FlexColumn small class="absolute bottom-0 w-72">
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import {identity, reject, whereEq, uniqBy, prop} from "ramda"
|
import {identity, reject, whereEq, uniqBy, prop} from "ramda"
|
||||||
import {onMount, onDestroy} from "svelte"
|
import {onMount, onDestroy} from "svelte"
|
||||||
import {derived} from "svelte/store"
|
|
||||||
import {quantify, batch} from "hurdak"
|
import {quantify, batch} from "hurdak"
|
||||||
import {fly, slide} from "src/util/transition"
|
import {fly, slide} from "src/util/transition"
|
||||||
import {replyKinds, isLike} from "src/util/nostr"
|
import {replyKinds, isLike} from "src/util/nostr"
|
||||||
@ -21,7 +20,6 @@
|
|||||||
import Spinner from "src/partials/Spinner.svelte"
|
import Spinner from "src/partials/Spinner.svelte"
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
import Card from "src/partials/Card.svelte"
|
import Card from "src/partials/Card.svelte"
|
||||||
import Chip from "src/partials/Chip.svelte"
|
|
||||||
import NoteMeta from "src/app/shared/NoteMeta.svelte"
|
import NoteMeta from "src/app/shared/NoteMeta.svelte"
|
||||||
import PersonCircle from "src/app/shared/PersonCircle.svelte"
|
import PersonCircle from "src/app/shared/PersonCircle.svelte"
|
||||||
import PersonName from "src/app/shared/PersonName.svelte"
|
import PersonName from "src/app/shared/PersonName.svelte"
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import cx from "classnames"
|
import cx from "classnames"
|
||||||
import {OS_MAP} from "bowser/src/constants"
|
|
||||||
import {nip19} from "nostr-tools"
|
import {nip19} from "nostr-tools"
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import {derived} from "svelte/store"
|
import {derived} from "svelte/store"
|
||||||
import {nthEq, last, sortBy} from "@welshman/lib"
|
import {last, sortBy} from "@welshman/lib"
|
||||||
import type {TrustedEvent, SignedEvent} from "@welshman/util"
|
import type {TrustedEvent, SignedEvent} from "@welshman/util"
|
||||||
import {
|
import {
|
||||||
LOCAL_RELAY_URL,
|
LOCAL_RELAY_URL,
|
||||||
@ -24,7 +23,6 @@
|
|||||||
import {showInfo} from "src/partials/Toast.svelte"
|
import {showInfo} from "src/partials/Toast.svelte"
|
||||||
import Icon from "src/partials/Icon.svelte"
|
import Icon from "src/partials/Icon.svelte"
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
import Popover2 from "src/partials/Popover2.svelte"
|
|
||||||
import WotScore from "src/partials/WotScore.svelte"
|
import WotScore from "src/partials/WotScore.svelte"
|
||||||
import Popover from "src/partials/Popover.svelte"
|
import Popover from "src/partials/Popover.svelte"
|
||||||
import ImageCircle from "src/partials/ImageCircle.svelte"
|
import ImageCircle from "src/partials/ImageCircle.svelte"
|
||||||
@ -103,14 +101,6 @@
|
|||||||
handlersShown = false
|
handlersShown = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const showHandlersPopover = () => {
|
|
||||||
handlersPopoverShown = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const hideHandlersPopover = () => {
|
|
||||||
handlersPopoverShown = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const os = browser.os.name.toLowerCase()
|
const os = browser.os.name.toLowerCase()
|
||||||
|
|
||||||
const createLabel = () => router.at("notes").of(note.id).at("label").open()
|
const createLabel = () => router.at("notes").of(note.id).at("label").open()
|
||||||
@ -219,7 +209,6 @@
|
|||||||
let view
|
let view
|
||||||
let actions = []
|
let actions = []
|
||||||
let handlersShown = false
|
let handlersShown = false
|
||||||
let handlersPopoverShown = false
|
|
||||||
|
|
||||||
$: disableActions =
|
$: disableActions =
|
||||||
!$canSign ||
|
!$canSign ||
|
||||||
@ -323,8 +312,7 @@
|
|||||||
<Popover theme="transparent" opts={{hideOnClick: true}}>
|
<Popover theme="transparent" opts={{hideOnClick: true}}>
|
||||||
<button
|
<button
|
||||||
slot="trigger"
|
slot="trigger"
|
||||||
class="relative flex items-center gap-1 pt-1 transition-all hover:pb-1 hover:pt-0 h-6"
|
class="relative flex h-6 items-center gap-1 pt-1 transition-all hover:pb-1 hover:pt-0">
|
||||||
on:click={showHandlersPopover}>
|
|
||||||
<i class="fa fa-up-right-from-square fa-sm" />
|
<i class="fa fa-up-right-from-square fa-sm" />
|
||||||
</button>
|
</button>
|
||||||
<div slot="tooltip" class="max-h-[300px] min-w-[180px] overflow-auto">
|
<div slot="tooltip" class="max-h-[300px] min-w-[180px] overflow-auto">
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import cx from "classnames"
|
import cx from "classnames"
|
||||||
import {derived} from "svelte/store"
|
import {derived} from "svelte/store"
|
||||||
import {themeColors} from "src/partials/state"
|
|
||||||
import Popover from "src/partials/Popover.svelte"
|
import Popover from "src/partials/Popover.svelte"
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
import WotScore from "src/partials/WotScore.svelte"
|
import WotScore from "src/partials/WotScore.svelte"
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import cx from "classnames"
|
import cx from "classnames"
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import {last, prop, objOf} from "ramda"
|
import {last, prop, objOf} from "ramda"
|
||||||
import {Handlerinformation, NostrConnect} from "nostr-tools/kinds"
|
import {HANDLER_INFORMATION, NOSTR_CONNECT} from "@welshman/util"
|
||||||
import {tryJson} from "src/util/misc"
|
import {tryJson} from "src/util/misc"
|
||||||
import {showWarning} from "src/partials/Toast.svelte"
|
import {showWarning} from "src/partials/Toast.svelte"
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
@ -14,15 +14,19 @@
|
|||||||
import {
|
import {
|
||||||
load,
|
load,
|
||||||
hints,
|
hints,
|
||||||
|
Amber,
|
||||||
loadHandle,
|
loadHandle,
|
||||||
getExtension,
|
getExtension,
|
||||||
withExtension,
|
withExtension,
|
||||||
|
loginWithAmber,
|
||||||
loginWithExtension,
|
loginWithExtension,
|
||||||
loginWithNostrConnect,
|
loginWithNostrConnect,
|
||||||
} from "src/engine"
|
} from "src/engine"
|
||||||
import {router} from "src/app/util/router"
|
import {router} from "src/app/util/router"
|
||||||
import {boot} from "src/app/state"
|
import {boot} from "src/app/state"
|
||||||
|
|
||||||
|
const amber = Amber.get()
|
||||||
|
|
||||||
const signUp = () => router.at("signup").replaceModal()
|
const signUp = () => router.at("signup").replaceModal()
|
||||||
|
|
||||||
const useBunker = () => router.at("login/bunker").replaceModal()
|
const useBunker = () => router.at("login/bunker").replaceModal()
|
||||||
@ -37,6 +41,15 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const useAmber = async () => {
|
||||||
|
const pubkey = await tryCatch(amber.getPubkey, e => showWarning(e.toString()))
|
||||||
|
|
||||||
|
if (pubkey) {
|
||||||
|
loginWithAmber(pubkey)
|
||||||
|
boot()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const usePrivateKey = () => router.at("login/privkey").replaceModal()
|
const usePrivateKey = () => router.at("login/privkey").replaceModal()
|
||||||
|
|
||||||
const usePublicKey = () => router.at("login/pubkey").replaceModal()
|
const usePublicKey = () => router.at("login/pubkey").replaceModal()
|
||||||
@ -53,7 +66,6 @@
|
|||||||
loading = true
|
loading = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// Fill in pubkey and relays if they entered a custom doain
|
// Fill in pubkey and relays if they entered a custom doain
|
||||||
if (!handler.pubkey) {
|
if (!handler.pubkey) {
|
||||||
const handle = await loadHandle(`_@${handler.domain}`)
|
const handle = await loadHandle(`_@${handler.domain}`)
|
||||||
@ -105,8 +117,8 @@
|
|||||||
relays: hints.ReadRelays().getUrls(),
|
relays: hints.ReadRelays().getUrls(),
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
kinds: [Handlerinformation],
|
kinds: [HANDLER_INFORMATION],
|
||||||
"#k": [NostrConnect.toString()],
|
"#k": [NOSTR_CONNECT.toString()],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
onEvent: async e => {
|
onEvent: async e => {
|
||||||
@ -183,6 +195,14 @@
|
|||||||
<span>Extension</span>
|
<span>Extension</span>
|
||||||
</Tile>
|
</Tile>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if amber.isEnabled()}
|
||||||
|
<Tile class="cursor-pointer bg-tinted-800" on:click={useAmber}>
|
||||||
|
<div>
|
||||||
|
<i class="fa fa-gem fa-xl" />
|
||||||
|
</div>
|
||||||
|
<span>Amber</span>
|
||||||
|
</Tile>
|
||||||
|
{/if}
|
||||||
<Tile class="cursor-pointer bg-tinted-800" on:click={usePrivateKey}>
|
<Tile class="cursor-pointer bg-tinted-800" on:click={usePrivateKey}>
|
||||||
<div>
|
<div>
|
||||||
<i class="fa fa-key fa-xl" />
|
<i class="fa fa-key fa-xl" />
|
||||||
|
@ -919,6 +919,8 @@ export const loginWithPublicKey = pubkey => addSession({method: "pubkey", pubkey
|
|||||||
|
|
||||||
export const loginWithExtension = pubkey => addSession({method: "extension", pubkey})
|
export const loginWithExtension = pubkey => addSession({method: "extension", pubkey})
|
||||||
|
|
||||||
|
export const loginWithAmber = pubkey => addSession({method: "amber", pubkey})
|
||||||
|
|
||||||
export const loginWithNsecBunker = async (pubkey, connectToken, connectRelay) => {
|
export const loginWithNsecBunker = async (pubkey, connectToken, connectRelay) => {
|
||||||
const connectKey = generatePrivateKey()
|
const connectKey = generatePrivateKey()
|
||||||
const connectHandler = {relays: [connectRelay]}
|
const connectHandler = {relays: [connectRelay]}
|
||||||
|
@ -36,7 +36,6 @@ import {
|
|||||||
projections,
|
projections,
|
||||||
sessions,
|
sessions,
|
||||||
hints,
|
hints,
|
||||||
ensureMessagePlaintext,
|
|
||||||
ensurePlaintext,
|
ensurePlaintext,
|
||||||
} from "src/engine/state"
|
} from "src/engine/state"
|
||||||
import {
|
import {
|
||||||
@ -370,7 +369,6 @@ projections.addHandler(14, handleChannelMessage)
|
|||||||
|
|
||||||
// Decrypt encrypted events eagerly
|
// Decrypt encrypted events eagerly
|
||||||
|
|
||||||
projections.addHandler(4, ensureMessagePlaintext)
|
|
||||||
projections.addHandler(FOLLOWS, ensurePlaintext)
|
projections.addHandler(FOLLOWS, ensurePlaintext)
|
||||||
projections.addHandler(MUTES, ensurePlaintext)
|
projections.addHandler(MUTES, ensurePlaintext)
|
||||||
|
|
||||||
|
@ -1467,7 +1467,6 @@ export const onAuth = async (url, challenge) => {
|
|||||||
|
|
||||||
seenChallenges.add(challenge)
|
seenChallenges.add(challenge)
|
||||||
|
|
||||||
console.log(url, challenge)
|
|
||||||
const event = await signer.get().signAsUser(
|
const event = await signer.get().signAsUser(
|
||||||
createEvent(22242, {
|
createEvent(22242, {
|
||||||
tags: [
|
tags: [
|
||||||
|
160
src/engine/utils/amber.ts
Normal file
160
src/engine/utils/amber.ts
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import {defer} from "hurdak"
|
||||||
|
import {sleep, tryCatch, Worker} from "@welshman/lib"
|
||||||
|
import type {HashedEvent, SignedEvent} from "@welshman/util"
|
||||||
|
import {hasValidSignature} from "@welshman/util"
|
||||||
|
import {parsePubkey} from "src/util/nostr"
|
||||||
|
|
||||||
|
const createGetPublicKeyIntent = () =>
|
||||||
|
`intent:#Intent;scheme=nostrsigner;S.compressionType=none;S.returnType=signature;S.type=get_public_key;end`
|
||||||
|
|
||||||
|
const createSignEventIntent = (draft: HashedEvent) =>
|
||||||
|
`intent:${encodeURIComponent(
|
||||||
|
JSON.stringify(draft),
|
||||||
|
)}#Intent;scheme=nostrsigner;S.compressionType=none;S.returnType=signature;S.type=sign_event;end`
|
||||||
|
|
||||||
|
const createNip04EncryptIntent = (pubkey: string, plainText: string) =>
|
||||||
|
`intent:${encodeURIComponent(
|
||||||
|
plainText,
|
||||||
|
)}#Intent;scheme=nostrsigner;S.pubKey=${pubkey};S.compressionType=none;S.returnType=signature;S.type=nip04_encrypt;end`
|
||||||
|
|
||||||
|
const createNip04DecryptIntent = (pubkey: string, data: string) =>
|
||||||
|
`intent:${encodeURIComponent(
|
||||||
|
data,
|
||||||
|
)}#Intent;scheme=nostrsigner;S.pubKey=${pubkey};S.compressionType=none;S.returnType=signature;S.type=nip04_decrypt;end`
|
||||||
|
|
||||||
|
const createNip44EncryptIntent = (pubkey: string, plainText: string) =>
|
||||||
|
`intent:${encodeURIComponent(
|
||||||
|
plainText,
|
||||||
|
)}#Intent;scheme=nostrsigner;S.pubKey=${pubkey};S.compressionType=none;S.returnType=signature;S.type=nip44_encrypt;end`
|
||||||
|
|
||||||
|
const createNip44DecryptIntent = (pubkey: string, data: string) =>
|
||||||
|
`intent:${encodeURIComponent(
|
||||||
|
data,
|
||||||
|
)}#Intent;scheme=nostrsigner;S.pubKey=${pubkey};S.compressionType=none;S.returnType=signature;S.type=nip44_decrypt;end`
|
||||||
|
|
||||||
|
class Request {
|
||||||
|
result = defer<{result?: string; error?: string}>()
|
||||||
|
|
||||||
|
constructor(readonly intent: string) {}
|
||||||
|
|
||||||
|
fulfill = async () => {
|
||||||
|
// Clear out the clipboard if we can
|
||||||
|
await tryCatch(() => navigator.clipboard.writeText(""))
|
||||||
|
|
||||||
|
// Send the intent to amber
|
||||||
|
const other = window.open(this.intent, "_blank")
|
||||||
|
|
||||||
|
// Wait a moment to avoid the visibilitychange listener firing before navigation
|
||||||
|
await sleep(500)
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
document.removeEventListener("visibilitychange", onVisibilityChange)
|
||||||
|
|
||||||
|
clearTimeout(timeout)
|
||||||
|
other.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onResult = result => {
|
||||||
|
this.result.resolve({result})
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onError = error => {
|
||||||
|
this.result.resolve({error})
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => onError("No data received."), 15000)
|
||||||
|
|
||||||
|
const onVisibilityChange = async () => {
|
||||||
|
await sleep(500)
|
||||||
|
|
||||||
|
if (document.visibilityState !== "visible") return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await navigator.clipboard.readText()
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
onResult(result)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Pass, document isn't focused
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("visibilitychange", onVisibilityChange)
|
||||||
|
|
||||||
|
return this.result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let singleton: Amber
|
||||||
|
|
||||||
|
export class Amber {
|
||||||
|
worker = new Worker<Request>()
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.worker.addGlobalHandler(request => request.fulfill())
|
||||||
|
}
|
||||||
|
|
||||||
|
static get() {
|
||||||
|
if (!singleton) {
|
||||||
|
singleton = new Amber()
|
||||||
|
}
|
||||||
|
|
||||||
|
return singleton
|
||||||
|
}
|
||||||
|
|
||||||
|
_request = async (intent: string) => {
|
||||||
|
const request = new Request(intent)
|
||||||
|
|
||||||
|
this.worker.push(request)
|
||||||
|
|
||||||
|
return request.result.then(({result, error}) => {
|
||||||
|
if (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
isEnabled = () => navigator.userAgent.includes("Android") && navigator.clipboard?.readText
|
||||||
|
|
||||||
|
getPubkey = async () => {
|
||||||
|
const result = await this._request(createGetPublicKeyIntent())
|
||||||
|
const pubkey = await parsePubkey(result)
|
||||||
|
|
||||||
|
if (!pubkey) {
|
||||||
|
throw new Error("Expected clipboard to have pubkey")
|
||||||
|
}
|
||||||
|
|
||||||
|
return pubkey
|
||||||
|
}
|
||||||
|
|
||||||
|
signEvent = async (draft: HashedEvent): Promise<SignedEvent> => {
|
||||||
|
const sig = await this._request(createSignEventIntent(draft))
|
||||||
|
|
||||||
|
if (!sig.match(/^[a-f0-9]+$/)) throw new Error("Expected hex signature")
|
||||||
|
|
||||||
|
const event: SignedEvent = {...draft, sig}
|
||||||
|
|
||||||
|
if (!hasValidSignature(event)) throw new Error("Invalid signature")
|
||||||
|
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
nip04Encrypt = (pubkey: string, plaintext: string): Promise<string> =>
|
||||||
|
this._request(createNip04EncryptIntent(pubkey, plaintext))
|
||||||
|
|
||||||
|
nip04Decrypt = (pubkey: string, data: string): Promise<string> =>
|
||||||
|
this._request(createNip04DecryptIntent(pubkey, data))
|
||||||
|
|
||||||
|
nip44Encrypt = (pubkey: string, plaintext: string): Promise<string> =>
|
||||||
|
this._request(createNip44EncryptIntent(pubkey, plaintext))
|
||||||
|
|
||||||
|
nip44Decrypt = (pubkey: string, data: string): Promise<string> =>
|
||||||
|
this._request(createNip44DecryptIntent(pubkey, data))
|
||||||
|
}
|
@ -5,6 +5,7 @@ import {Nip59} from "./nip59"
|
|||||||
import {Signer} from "./signer"
|
import {Signer} from "./signer"
|
||||||
import {Connect} from "./connect"
|
import {Connect} from "./connect"
|
||||||
|
|
||||||
|
export * from "./amber"
|
||||||
export * from "./nip04"
|
export * from "./nip04"
|
||||||
export * from "./nip07"
|
export * from "./nip07"
|
||||||
export * from "./nip44"
|
export * from "./nip44"
|
||||||
|
@ -3,6 +3,7 @@ import {switcherFn, tryFunc} from "hurdak"
|
|||||||
import type {Session} from "src/engine/model"
|
import type {Session} from "src/engine/model"
|
||||||
import type {Connect} from "./connect"
|
import type {Connect} from "./connect"
|
||||||
import {withExtension} from "./nip07"
|
import {withExtension} from "./nip07"
|
||||||
|
import {Amber} from "./amber"
|
||||||
|
|
||||||
export class Nip04 {
|
export class Nip04 {
|
||||||
constructor(
|
constructor(
|
||||||
@ -11,7 +12,7 @@ export class Nip04 {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
isEnabled() {
|
isEnabled() {
|
||||||
return ["privkey", "connect", "extension"].includes(this.session?.method)
|
return ["amber", "privkey", "connect", "extension"].includes(this.session?.method)
|
||||||
}
|
}
|
||||||
|
|
||||||
async encrypt(message: string, pk: string, sk: string) {
|
async encrypt(message: string, pk: string, sk: string) {
|
||||||
@ -26,6 +27,7 @@ export class Nip04 {
|
|||||||
const {method, privkey} = this.session
|
const {method, privkey} = this.session
|
||||||
|
|
||||||
return switcherFn(method, {
|
return switcherFn(method, {
|
||||||
|
amber: () => Amber.get().nip04Encrypt(pk, message),
|
||||||
privkey: () => this.encrypt(message, pk, privkey),
|
privkey: () => this.encrypt(message, pk, privkey),
|
||||||
extension: () => withExtension(ext => ext.nip04.encrypt(pk, message)),
|
extension: () => withExtension(ext => ext.nip04.encrypt(pk, message)),
|
||||||
connect: () => this.connect.broker.nip04Encrypt(pk, message),
|
connect: () => this.connect.broker.nip04Encrypt(pk, message),
|
||||||
@ -36,6 +38,7 @@ export class Nip04 {
|
|||||||
const {method, privkey} = this.session
|
const {method, privkey} = this.session
|
||||||
|
|
||||||
return switcherFn(method, {
|
return switcherFn(method, {
|
||||||
|
amber: () => Amber.get().nip04Decrypt(pk, message),
|
||||||
privkey: () => this.decrypt(message, pk, privkey),
|
privkey: () => this.decrypt(message, pk, privkey),
|
||||||
extension: () => withExtension(ext => ext.nip04.decrypt(pk, message)),
|
extension: () => withExtension(ext => ext.nip04.decrypt(pk, message)),
|
||||||
connect: () => this.connect.broker.nip04Decrypt(pk, message),
|
connect: () => this.connect.broker.nip04Decrypt(pk, message),
|
||||||
|
@ -6,6 +6,7 @@ import {fromHex} from "src/util/nostr"
|
|||||||
import type {Session} from "src/engine/model"
|
import type {Session} from "src/engine/model"
|
||||||
import type {Connect} from "./connect"
|
import type {Connect} from "./connect"
|
||||||
import {withExtension} from "./nip07"
|
import {withExtension} from "./nip07"
|
||||||
|
import {Amber} from "./amber"
|
||||||
|
|
||||||
// Deriving shared secret is an expensive computation, cache it
|
// Deriving shared secret is an expensive computation, cache it
|
||||||
export const getSharedSecret = cached({
|
export const getSharedSecret = cached({
|
||||||
@ -21,7 +22,7 @@ export class Nip44 {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
isEnabled() {
|
isEnabled() {
|
||||||
if (["privkey", "connect"].includes(this.session?.method)) {
|
if (["amber", "privkey", "connect"].includes(this.session?.method)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,6 +45,7 @@ export class Nip44 {
|
|||||||
const {method, privkey} = this.session
|
const {method, privkey} = this.session
|
||||||
|
|
||||||
return switcherFn(method, {
|
return switcherFn(method, {
|
||||||
|
amber: () => Amber.get().nip44Encrypt(pk, message),
|
||||||
privkey: () => this.encrypt(message, pk, privkey),
|
privkey: () => this.encrypt(message, pk, privkey),
|
||||||
extension: () => withExtension(ext => ext.nip44.encrypt(pk, message)),
|
extension: () => withExtension(ext => ext.nip44.encrypt(pk, message)),
|
||||||
connect: () => this.connect.broker.nip44Encrypt(pk, message),
|
connect: () => this.connect.broker.nip44Encrypt(pk, message),
|
||||||
@ -54,6 +56,7 @@ export class Nip44 {
|
|||||||
const {method, privkey} = this.session
|
const {method, privkey} = this.session
|
||||||
|
|
||||||
return switcherFn(method, {
|
return switcherFn(method, {
|
||||||
|
amber: () => Amber.get().nip44Decrypt(pk, message),
|
||||||
privkey: () => this.decrypt(message, pk, privkey),
|
privkey: () => this.decrypt(message, pk, privkey),
|
||||||
extension: () => withExtension(ext => ext.nip44.decrypt(pk, message)),
|
extension: () => withExtension(ext => ext.nip44.decrypt(pk, message)),
|
||||||
connect: () => this.connect.broker.nip44Decrypt(pk, message),
|
connect: () => this.connect.broker.nip44Decrypt(pk, message),
|
||||||
|
@ -5,6 +5,7 @@ import {getPublicKey, getSignature} from "src/util/nostr"
|
|||||||
import type {Session} from "src/engine/model"
|
import type {Session} from "src/engine/model"
|
||||||
import type {Connect} from "./connect"
|
import type {Connect} from "./connect"
|
||||||
import {withExtension} from "./nip07"
|
import {withExtension} from "./nip07"
|
||||||
|
import {Amber} from "./amber"
|
||||||
|
|
||||||
export class Signer {
|
export class Signer {
|
||||||
constructor(
|
constructor(
|
||||||
@ -13,7 +14,7 @@ export class Signer {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
isEnabled() {
|
isEnabled() {
|
||||||
return ["connect", "privkey", "extension"].includes(this.session?.method)
|
return ["amber", "connect", "privkey", "extension"].includes(this.session?.method)
|
||||||
}
|
}
|
||||||
|
|
||||||
prepWithKey(event: EventTemplate, sk: string) {
|
prepWithKey(event: EventTemplate, sk: string) {
|
||||||
@ -47,6 +48,7 @@ export class Signer {
|
|||||||
const event = this.prepAsUser(template)
|
const event = this.prepAsUser(template)
|
||||||
|
|
||||||
return switcherFn(method, {
|
return switcherFn(method, {
|
||||||
|
amber: () => Amber.get().signEvent(event),
|
||||||
privkey: () => ({...event, sig: getSignature(event, privkey)}),
|
privkey: () => ({...event, sig: getSignature(event, privkey)}),
|
||||||
extension: () => withExtension(ext => ext.signEvent(event)),
|
extension: () => withExtension(ext => ext.signEvent(event)),
|
||||||
connect: () => this.connect.broker.signEvent(template),
|
connect: () => this.connect.broker.signEvent(template),
|
||||||
|
@ -1,24 +1,24 @@
|
|||||||
import fs from 'fs'
|
import fs from "fs"
|
||||||
import dotenv from 'dotenv'
|
import dotenv from "dotenv"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import {defineConfig} from "vite"
|
import {defineConfig} from "vite"
|
||||||
import {VitePWA} from "vite-plugin-pwa"
|
import {VitePWA} from "vite-plugin-pwa"
|
||||||
import mkcert from "vite-plugin-mkcert"
|
import mkcert from "vite-plugin-mkcert"
|
||||||
import {favicons} from 'favicons'
|
import {favicons} from "favicons"
|
||||||
import htmlPlugin from "vite-plugin-html-config"
|
import htmlPlugin from "vite-plugin-html-config"
|
||||||
import sveltePreprocess from "svelte-preprocess"
|
import sveltePreprocess from "svelte-preprocess"
|
||||||
import {svelte} from "@sveltejs/vite-plugin-svelte"
|
import {svelte} from "@sveltejs/vite-plugin-svelte"
|
||||||
import {nodePolyfills} from "vite-plugin-node-polyfills"
|
import {nodePolyfills} from "vite-plugin-node-polyfills"
|
||||||
|
|
||||||
dotenv.config({path: '.env.local'})
|
dotenv.config({path: ".env.local"})
|
||||||
dotenv.config({path: '.env'})
|
dotenv.config({path: ".env"})
|
||||||
|
|
||||||
const accentColor = process.env.VITE_LIGHT_THEME.match(/accent:(#\w+)/)[1]
|
const accentColor = process.env.VITE_LIGHT_THEME.match(/accent:(#\w+)/)[1]
|
||||||
|
|
||||||
export default defineConfig(async () => {
|
export default defineConfig(async () => {
|
||||||
const icons = await favicons('public' + process.env.VITE_APP_LOGO)
|
const icons = await favicons("public" + process.env.VITE_APP_LOGO)
|
||||||
|
|
||||||
if (!fs.existsSync('public/icons')) fs.mkdirSync('public/icons')
|
if (!fs.existsSync("public/icons")) fs.mkdirSync("public/icons")
|
||||||
|
|
||||||
for (const {name, contents} of icons.images) {
|
for (const {name, contents} of icons.images) {
|
||||||
fs.writeFileSync(`public/icons/${name}`, contents, "binary")
|
fs.writeFileSync(`public/icons/${name}`, contents, "binary")
|
||||||
@ -72,19 +72,49 @@ export default defineConfig(async () => {
|
|||||||
{rel: "apple-touch-icon", sizes: "144x144", href: "/icons/apple-touch-icon-144x144.png"},
|
{rel: "apple-touch-icon", sizes: "144x144", href: "/icons/apple-touch-icon-144x144.png"},
|
||||||
{rel: "apple-touch-icon", sizes: "152x152", href: "/icons/apple-touch-icon-152x152.png"},
|
{rel: "apple-touch-icon", sizes: "152x152", href: "/icons/apple-touch-icon-152x152.png"},
|
||||||
{rel: "apple-touch-icon", sizes: "180x180", href: "/icons/apple-touch-icon-180x180.png"},
|
{rel: "apple-touch-icon", sizes: "180x180", href: "/icons/apple-touch-icon-180x180.png"},
|
||||||
{rel: "icon", type: "image/png", sizes: "192x192", href: "/icons/android-icon-192x192.png"},
|
{
|
||||||
|
rel: "icon",
|
||||||
|
type: "image/png",
|
||||||
|
sizes: "192x192",
|
||||||
|
href: "/icons/android-icon-192x192.png",
|
||||||
|
},
|
||||||
{rel: "icon", type: "image/png", sizes: "32x32", href: "/icons/favicon-32x32.png"},
|
{rel: "icon", type: "image/png", sizes: "32x32", href: "/icons/favicon-32x32.png"},
|
||||||
{rel: "icon", type: "image/png", sizes: "96x96", href: "/icons/favicon-96x96.png"},
|
{rel: "icon", type: "image/png", sizes: "96x96", href: "/icons/favicon-96x96.png"},
|
||||||
{rel: "icon", type: "image/png", sizes: "16x16", href: "/icons/favicon-16x16.png"},
|
{rel: "icon", type: "image/png", sizes: "16x16", href: "/icons/favicon-16x16.png"},
|
||||||
{rel: "mask-icon", href: "/images/logo.svg", color: "#FFFFFF"},
|
{rel: "mask-icon", href: "/images/logo.svg", color: "#FFFFFF"},
|
||||||
|
|
||||||
{rel: "icon", type: "image/png", sizes: "144x144", href: "/icons/android-chrome-144x144.png"},
|
{
|
||||||
{rel: "icon", type: "image/png", sizes: "192x192", href: "/icons/android-chrome-192x192.png"},
|
rel: "icon",
|
||||||
{rel: "icon", type: "image/png", sizes: "256x256", href: "/icons/android-chrome-256x256.png"},
|
type: "image/png",
|
||||||
|
sizes: "144x144",
|
||||||
|
href: "/icons/android-chrome-144x144.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rel: "icon",
|
||||||
|
type: "image/png",
|
||||||
|
sizes: "192x192",
|
||||||
|
href: "/icons/android-chrome-192x192.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rel: "icon",
|
||||||
|
type: "image/png",
|
||||||
|
sizes: "256x256",
|
||||||
|
href: "/icons/android-chrome-256x256.png",
|
||||||
|
},
|
||||||
{rel: "icon", type: "image/png", sizes: "36x36", href: "/icons/android-chrome-36x36.png"},
|
{rel: "icon", type: "image/png", sizes: "36x36", href: "/icons/android-chrome-36x36.png"},
|
||||||
{rel: "icon", type: "image/png", sizes: "384x384", href: "/icons/android-chrome-384x384.png"},
|
{
|
||||||
|
rel: "icon",
|
||||||
|
type: "image/png",
|
||||||
|
sizes: "384x384",
|
||||||
|
href: "/icons/android-chrome-384x384.png",
|
||||||
|
},
|
||||||
{rel: "icon", type: "image/png", sizes: "48x48", href: "/icons/android-chrome-48x48.png"},
|
{rel: "icon", type: "image/png", sizes: "48x48", href: "/icons/android-chrome-48x48.png"},
|
||||||
{rel: "icon", type: "image/png", sizes: "512x512", href: "/icons/android-chrome-512x512.png"},
|
{
|
||||||
|
rel: "icon",
|
||||||
|
type: "image/png",
|
||||||
|
sizes: "512x512",
|
||||||
|
href: "/icons/android-chrome-512x512.png",
|
||||||
|
},
|
||||||
{rel: "icon", type: "image/png", sizes: "72x72", href: "/icons/android-chrome-72x72.png"},
|
{rel: "icon", type: "image/png", sizes: "72x72", href: "/icons/android-chrome-72x72.png"},
|
||||||
{rel: "icon", type: "image/png", sizes: "96x96", href: "/icons/android-chrome-96x96.png"},
|
{rel: "icon", type: "image/png", sizes: "96x96", href: "/icons/android-chrome-96x96.png"},
|
||||||
{rel: "apple-touch-icon", sizes: "1024x1024", href: "apple-touch-icon-1024x1024.png"},
|
{rel: "apple-touch-icon", sizes: "1024x1024", href: "apple-touch-icon-1024x1024.png"},
|
||||||
@ -109,11 +139,17 @@ export default defineConfig(async () => {
|
|||||||
description: process.env.VITE_APP_DESCRIPTION,
|
description: process.env.VITE_APP_DESCRIPTION,
|
||||||
theme_color: accentColor,
|
theme_color: accentColor,
|
||||||
protocol_handlers: [{protocol: "web+nostr", url: "/%s"}],
|
protocol_handlers: [{protocol: "web+nostr", url: "/%s"}],
|
||||||
|
permissions: ["clipboardRead", "clipboardWrite", "unlimitedStorage"],
|
||||||
icons: [
|
icons: [
|
||||||
{src: "images/pwa-64x64.png", sizes: "64x64", type: "image/png"},
|
{src: "images/pwa-64x64.png", sizes: "64x64", type: "image/png"},
|
||||||
{src: "images/pwa-192x192.png", sizes: "192x192", type: "image/png"},
|
{src: "images/pwa-192x192.png", sizes: "192x192", type: "image/png"},
|
||||||
{src: "images/pwa-512x512.png", sizes: "512x512", type: "image/png", purpose: "any"},
|
{src: "images/pwa-512x512.png", sizes: "512x512", type: "image/png", purpose: "any"},
|
||||||
{src: "images/maskable-icon-512x512.png", sizes: "512x512", type: "image/png", purpose: "maskable"},
|
{
|
||||||
|
src: "images/maskable-icon-512x512.png",
|
||||||
|
sizes: "512x512",
|
||||||
|
type: "image/png",
|
||||||
|
purpose: "maskable",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
Loading…
Reference in New Issue
Block a user