diff --git a/static/components/chat-dialog/chat-dialog.html b/static/components/chat-dialog/chat-dialog.html
new file mode 100644
index 0000000..9ab5296
--- /dev/null
+++ b/static/components/chat-dialog/chat-dialog.html
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+ Chat Box
+
+
+
+ Minimize
+
+
+ Maximize
+
+
+ Close
+
+
+
+
+
+
+
+
+
diff --git a/static/components/chat-dialog/chat-dialog.js b/static/components/chat-dialog/chat-dialog.js
new file mode 100644
index 0000000..96f82f6
--- /dev/null
+++ b/static/components/chat-dialog/chat-dialog.js
@@ -0,0 +1,143 @@
+async function chatDialog(path) {
+ const template = await loadTemplateAsync(path)
+
+ Vue.component('chat-dialog', {
+ name: 'chat-dialog',
+ template,
+
+ props: ['account', 'merchant', 'relays'],
+ data: function () {
+ return {
+ dialog: false,
+ maximizedToggle: true,
+ pool: null,
+ nostrMessages: [],
+ newMessage: ''
+ }
+ },
+ computed: {
+ sortedMessages() {
+ return this.nostrMessages.sort((a, b) => a.timestamp - b.timestamp)
+ }
+ },
+ methods: {
+ async startPool() {
+ let sub = this.pool.sub(Array.from(this.relays), [
+ {
+ kinds: [4],
+ authors: [this.account.pubkey]
+ },
+ {
+ kinds: [4],
+ '#p': [this.account.pubkey]
+ }
+ ])
+ sub.on('event', async event => {
+ let mine = event.pubkey == this.account.pubkey
+ let sender = mine
+ ? event.tags.find(([k, v]) => k === 'p' && v && v !== '')[1]
+ : event.pubkey
+
+ try {
+ let plaintext
+ if (this.account.privkey) {
+ plaintext = await NostrTools.nip04.decrypt(
+ this.account.privkey,
+ sender,
+ event.content
+ )
+ } else if (this.account.useExtension && this.hasNip07) {
+ plaintext = await window.nostr.nip04.decrypt(
+ sender,
+ event.content
+ )
+ }
+ this.nostrMessages.push({
+ id: event.id,
+ msg: plaintext,
+ timestamp: event.created_at,
+ sender: `${mine ? 'Me' : 'Merchant'}`
+ })
+ } catch {
+ console.error('Unable to decrypt message!')
+ }
+ })
+ },
+ async sendMessage() {
+ if (this.newMessage && this.newMessage.length < 1) return
+ let event = {
+ ...(await NostrTools.getBlankEvent()),
+ kind: 4,
+ created_at: Math.floor(Date.now() / 1000),
+ tags: [['p', this.merchant]],
+ pubkey: this.account.pubkey,
+ content: await this.encryptMsg()
+ }
+ event.id = NostrTools.getEventHash(event)
+ event.sig = this.signEvent(event)
+ for (const url of Array.from(this.relays)) {
+ try {
+ let relay = NostrTools.relayInit(url)
+ relay.on('connect', () => {
+ console.debug(`connected to ${relay.url}`)
+ })
+ relay.on('error', () => {
+ console.debug(`failed to connect to ${relay.url}`)
+ })
+
+ await relay.connect()
+ let pub = relay.publish(event)
+ pub.on('ok', () => {
+ console.debug(`${relay.url} has accepted our event`)
+ relay.close()
+ })
+ pub.on('failed', reason => {
+ console.debug(`failed to publish to ${relay.url}: ${reason}`)
+ relay.close()
+ })
+ this.newMessage = ''
+ } catch (e) {
+ console.error(e)
+ }
+ }
+ },
+ async encryptMsg() {
+ try {
+ let cypher
+ if (this.account.privkey) {
+ cypher = await NostrTools.nip04.encrypt(
+ this.account.privkey,
+ this.merchant,
+ this.newMessage
+ )
+ } else if (this.account.useExtension && this.hasNip07) {
+ cypher = await window.nostr.nip04.encrypt(
+ this.merchant,
+ this.newMessage
+ )
+ }
+ return cypher
+ } catch (e) {
+ console.error(e)
+ }
+ },
+ async signEvent(event) {
+ if (this.account.privkey) {
+ event.sig = await NostrTools.signEvent(event, this.account.privkey)
+ } else if (this.account.useExtension && this.hasNip07) {
+ event = await window.nostr.signEvent(event)
+ }
+ return event
+ }
+ },
+ created() {
+ this.pool = new NostrTools.SimplePool()
+ setTimeout(() => {
+ if (window.nostr) {
+ this.hasNip07 = true
+ }
+ }, 1000)
+ this.startPool()
+ }
+ })
+}