From a39efe177c6ef6d48eabd8bc1f15b32723782904 Mon Sep 17 00:00:00 2001 From: Attila Gazso <230163+agazso@users.noreply.github.com> Date: Tue, 21 Nov 2023 18:12:21 +0100 Subject: [PATCH 1/5] wip: install invite flow --- src/lib/adapters/index.ts | 1 + src/lib/adapters/waku/crypto.ts | 5 + src/lib/adapters/waku/index.ts | 115 +++++++++++++++- src/lib/adapters/waku/types.ts | 1 + src/lib/components/chat-object-invite.svelte | 94 +++++++++++++ src/lib/components/icons/data-blob.svelte | 36 +++++ .../{error-modal.svelte => modal.svelte} | 0 src/lib/objects/lookup.ts | 30 ++-- src/lib/routes.ts | 2 + src/lib/stores/chat.ts | 9 +- src/lib/stores/installed-objects.ts | 55 +++++++- src/routes/+layout.svelte | 2 +- src/routes/+page.svelte | 4 +- src/routes/chat/[id]/+page.svelte | 10 ++ src/routes/chat/[id]/object/new/+page.svelte | 129 +++++++++++++++--- src/routes/identity/+page.svelte | 35 ++--- src/routes/objects/+page.svelte | 101 ++++++++++++++ src/routes/objects/[object_id]/+page.svelte | 75 ++++++++++ 18 files changed, 649 insertions(+), 55 deletions(-) create mode 100644 src/lib/components/chat-object-invite.svelte create mode 100644 src/lib/components/icons/data-blob.svelte rename src/lib/components/{error-modal.svelte => modal.svelte} (100%) create mode 100644 src/routes/objects/+page.svelte create mode 100644 src/routes/objects/[object_id]/+page.svelte diff --git a/src/lib/adapters/index.ts b/src/lib/adapters/index.ts index b6325018..c4ed7da0 100644 --- a/src/lib/adapters/index.ts +++ b/src/lib/adapters/index.ts @@ -36,6 +36,7 @@ export interface Adapter { data: JSONSerializable, ): Promise sendGroupChatInvite(wallet: BaseWallet, chatId: string, users: string[]): Promise + sendInstall(chatId: string, objectId: string, command: 'invite' | 'accept'): Promise updateStore( address: string, diff --git a/src/lib/adapters/waku/crypto.ts b/src/lib/adapters/waku/crypto.ts index 6816c1ad..b4a0be7d 100644 --- a/src/lib/adapters/waku/crypto.ts +++ b/src/lib/adapters/waku/crypto.ts @@ -38,6 +38,11 @@ export function hash(data: Uint8Array | Hex): Hex { return keccak256(bytes) } +export function hashString(s: string): Hex { + const data = new TextEncoder().encode(s) + return hash(data) +} + export function encrypt( plaintext: Uint8Array, key: Uint8Array, diff --git a/src/lib/adapters/waku/index.ts b/src/lib/adapters/waku/index.ts index 7bc173c6..21fed29b 100644 --- a/src/lib/adapters/waku/index.ts +++ b/src/lib/adapters/waku/index.ts @@ -13,6 +13,8 @@ import { type WithoutMeta, type ChatMessage, type BabbleMessage, + type WithMeta, + type InstallMessage, } from '$lib/stores/chat' import type { User } from '$lib/types' import type { TimeFilter } from '@waku/interfaces' @@ -38,6 +40,7 @@ import { makeWakustore, type Wakustore } from './wakustore' import type { StorageChat, StorageChatEntry, + StorageInstalledObject, StorageInstalledObjectEntry, StorageObjectEntry, StorageProfile, @@ -62,6 +65,7 @@ import { } from './codec' import type { DecodedMessage } from '@waku/message-encryption' import { utils } from '@noble/secp256k1' +import { getObjectSpec } from '$lib/objects/external/lib' const MAX_MESSAGES = 100 @@ -148,6 +152,10 @@ async function addMessageToChat( await executeOnDataMessage(ownPublicKey, blockchainAdapter, chatId, message, send) } + if (message.type === 'install') { + await executeOnInstallMessage(ownPublicKey, chatId, message) + } + const unread = message.type === 'user' && message.senderPublicKey !== ownPublicKey ? 1 : 0 chats.updateChat(chatId, (chat) => ({ ...chat, @@ -160,7 +168,7 @@ async function executeOnDataMessage( publicKey: string, blockchainAdapter: WakuObjectAdapter, chatId: string, - dataMessage: DataMessage, + dataMessage: WithMeta, send: (data: JSONValue) => Promise, ) { const descriptor = lookup(dataMessage.objectId) @@ -218,6 +226,85 @@ async function executeOnDataMessage( } } +async function executeOnInstallMessage( + publicKey: string, + chatId: string, + message: WithMeta, +) { + if (message.senderPublicKey === publicKey) { + if (message.command === 'accept') { + const installedObjects = get(installedObjectStore).objects + if (!installedObjects.has(message.objectId)) { + return + } + + installedObjectStore.updateInstalledObject(message.objectId, (object) => ({ + ...object, + installed: true, + })) + + // add to chat objects + chats.updateChat(chatId, (chat) => { + if (!chat.objects) { + chat.objects = [] + } + if (!chat.objects.includes(message.objectId)) { + chat.objects.push(message.objectId) + } + return chat + }) + } + + return + } + + if (message.command === 'invite') { + // add to installed objects + const installedObjects = get(installedObjectStore).objects + if (installedObjects.has(message.objectId)) { + return + } + + // TODO fix WakuScriptType + const objectSpec = await getObjectSpec(message.objectId, 'chat') + if (!objectSpec) { + return + } + + const installedObject: StorageInstalledObject = { + objectId: message.objectId, + name: objectSpec.object.name, + description: objectSpec.object.description, + // TODO fix relative path to absolute + logo: objectSpec.object.files.logo.path, + installed: false, + } + + installedObjectStore.addInstalledObject(installedObject) + } else if (message.command === 'accept') { + const installedObjects = get(installedObjectStore).objects + if (!installedObjects.has(message.objectId)) { + return + } + + installedObjectStore.updateInstalledObject(message.objectId, (object) => ({ + ...object, + installed: true, + })) + + // add to chat objects + chats.updateChat(chatId, (chat) => { + if (!chat.objects) { + chat.objects = [] + } + if (!chat.objects.includes(message.objectId)) { + chat.objects.push(message.objectId) + } + return chat + }) + } +} + function decryptHexToString(h: string, symKey: Uint8Array): string { const encrypted = hexToBytes(h) const decrypted = decrypt(encrypted, symKey) @@ -677,6 +764,23 @@ export default class WakuAdapter implements Adapter { } } + async sendInstall(chatId: string, objectId: string, command: 'invite' | 'accept'): Promise { + const wallet = get(walletStore).wallet + if (!wallet) { + return + } + + const senderPrivateKey = wallet.privateKey + const message: WithoutMeta = { + type: 'install', + objectId, + command, + } + const encryptionKey = hexToBytes(chatId) + + await this.safeWaku.sendMessage(message, encryptionKey, hexToBytes(senderPrivateKey)) + } + async updateStore( // eslint-disable-next-line @typescript-eslint/no-unused-vars _address: string, @@ -928,7 +1032,14 @@ export default class WakuAdapter implements Adapter { adapter: WakuObjectAdapter, ) { // only handle certain types of messages - if (!(message.type === 'invite' || message.type === 'data' || message.type === 'user')) { + if ( + !( + message.type === 'invite' || + message.type === 'data' || + message.type === 'user' || + message.type === 'install' + ) + ) { return } diff --git a/src/lib/adapters/waku/types.ts b/src/lib/adapters/waku/types.ts index ca83dcd2..dc28e6c0 100644 --- a/src/lib/adapters/waku/types.ts +++ b/src/lib/adapters/waku/types.ts @@ -21,5 +21,6 @@ export interface StorageInstalledObject { name: string description: string logo: string + installed: boolean } export type StorageInstalledObjectEntry = [objectId: string, object: StorageInstalledObject] diff --git a/src/lib/components/chat-object-invite.svelte b/src/lib/components/chat-object-invite.svelte new file mode 100644 index 00000000..3dd8aa84 --- /dev/null +++ b/src/lib/components/chat-object-invite.svelte @@ -0,0 +1,94 @@ + + + + {#if !object} + + {:else} +
+ + {#if message.command === 'invite'} + {senderName} invited {recipientName} to use "{object.name}" in this chat. + {:else if message.command === 'accept'} + {senderName} accepted the invite. You can now use "{object.name}" in this chat. + {/if} + + + + + + {#if message.command === 'invite'} + {#if myMessage} +

+ {#if isInstalledInChat} + Invite accepted + {:else} + Invite pending... + {/if} +

+ {:else} + + {/if} + {:else if message.command === 'accept'} +

+ {#if isInstalledInChat} + Invite accepted + {:else} + Invite pending... + {/if} +

+ {/if} +
+
+ {/if} +
+ + diff --git a/src/lib/components/icons/data-blob.svelte b/src/lib/components/icons/data-blob.svelte new file mode 100644 index 00000000..4e24717d --- /dev/null +++ b/src/lib/components/icons/data-blob.svelte @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/src/lib/components/error-modal.svelte b/src/lib/components/modal.svelte similarity index 100% rename from src/lib/components/error-modal.svelte rename to src/lib/components/modal.svelte diff --git a/src/lib/objects/lookup.ts b/src/lib/objects/lookup.ts index c257b06d..f0872d07 100644 --- a/src/lib/objects/lookup.ts +++ b/src/lib/objects/lookup.ts @@ -8,6 +8,11 @@ import { splitDescriptor } from './split' import { installedObjectStore } from '$lib/stores/installed-objects' import { get } from 'svelte/store' +export type InstalledObjectDescriptor = WakuObjectSvelteDescriptor & { + preInstalled: boolean + installed: boolean +} + const preInstalledObjectList: WakuObjectSvelteDescriptor[] = [ helloWorldDescriptor, payggyDescriptor, @@ -20,16 +25,25 @@ const preInstalledObjectList: WakuObjectSvelteDescriptor[] = [ ), ] -export function lookup(objectId: string): WakuObjectSvelteDescriptor | undefined { +export function lookup(objectId: string): InstalledObjectDescriptor | undefined { const installedObjectList = getInstalledObjectList() return installedObjectList.find((object) => object.objectId === objectId) } -export function getInstalledObjectList() { - const installedObjectList = Array.from(get(installedObjectStore).objects) - .map((item) => item[1]) - .map((object) => - getExternalDescriptor(object.objectId, object.name, object.description, object.logo), - ) - return preInstalledObjectList.concat(installedObjectList) +export function getInstalledObjectList(): InstalledObjectDescriptor[] { + const installedObjectList = Array.from(get(installedObjectStore).objects).map((item) => { + const object = item[1] + return { + ...getExternalDescriptor(object.objectId, object.name, object.description, object.logo), + preInstalled: false, + installed: object.installed, + } + }) + return preInstalledObjectList + .map((object) => ({ + ...object, + preInstalled: true, + installed: true, + })) + .concat(installedObjectList) } diff --git a/src/lib/routes.ts b/src/lib/routes.ts index 689eb3bb..1b6d0aa6 100644 --- a/src/lib/routes.ts +++ b/src/lib/routes.ts @@ -20,4 +20,6 @@ export default { BABBLES_CHAT: (id: string, threadId?: string) => `/babbles/chat/${id}${threadId ? `/${threadId}` : ''}`, BABBLES_EDIT: (id: string) => `/babbles/chat/${id}/edit`, + SETTINGS_OBJECTS: '/objects', + SETTINGS_OBJECT: (objectId: string) => `/objects/${objectId}`, } diff --git a/src/lib/stores/chat.ts b/src/lib/stores/chat.ts index a7c1ce62..f66ce284 100644 --- a/src/lib/stores/chat.ts +++ b/src/lib/stores/chat.ts @@ -40,7 +40,13 @@ export interface BabbleMessage { parentId?: string } -export type Message = UserMessage | DataMessage | InviteMessage | BabbleMessage +export interface InstallMessage { + type: 'install' + objectId: string + command: 'invite' | 'accept' +} + +export type Message = UserMessage | DataMessage | InviteMessage | BabbleMessage | InstallMessage export type WithoutMeta = Omit export type WithMeta = T & MessageMetadata @@ -58,6 +64,7 @@ export interface Chat { avatar?: string joined?: boolean inviter?: string + objects?: string[] } export interface ChatData { diff --git a/src/lib/stores/installed-objects.ts b/src/lib/stores/installed-objects.ts index dccacc32..bc0a3147 100644 --- a/src/lib/stores/installed-objects.ts +++ b/src/lib/stores/installed-objects.ts @@ -8,7 +8,14 @@ export interface InstalledObjects { } // eslint-disable-next-line @typescript-eslint/no-empty-interface -interface InstalledObjectStore extends Writable {} +interface InstalledObjectStore extends Writable { + addInstalledObject: (object: StorageInstalledObject) => void + updateInstalledObject: ( + objectId: string, + update: (object: StorageInstalledObject) => StorageInstalledObject, + ) => void + removeInstalledObject: (objectId: string) => void +} function createInstalledObjects(): InstalledObjectStore { const store = writable({ @@ -17,6 +24,52 @@ function createInstalledObjects(): InstalledObjectStore { }) return { ...store, + addInstalledObject(object: StorageInstalledObject) { + store.update((state) => { + if (state.objects.has(object.objectId)) { + return state + } + + state.objects.set(object.objectId, object) + + return { + ...state, + objects: state.objects, + } + }) + }, + updateInstalledObject(objectId, update) { + store.update((state) => { + if (!state.objects.has(objectId)) { + return state + } + const oldObject = state.objects.get(objectId) + if (!oldObject) { + return state + } + const newMap = new Map(state.objects) + const newObject = update(oldObject) + newMap.set(objectId, newObject) + + return { + ...state, + objects: newMap, + } + }) + }, + removeInstalledObject(objectId) { + store.update((state) => { + if (!state.objects.has(objectId)) { + return state + } + state.objects.delete(objectId) + + return { + ...state, + chats: state.objects, + } + }) + }, } } diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index f9695e95..3a63ff95 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -17,7 +17,7 @@ import { defaultBlockchainNetwork, getChainId } from '$lib/adapters/transaction' import Container from '$lib/components/container.svelte' import Loading from '$lib/components/loading.svelte' - import ErrorModal from '$lib/components/error-modal.svelte' + import ErrorModal from '$lib/components/modal.svelte' import Button from '$lib/components/button.svelte' import Renew from '$lib/components/icons/renew.svelte' diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 2ed941dc..bfbea352 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -15,7 +15,7 @@ // Stores import { profile } from '$lib/stores/profile' - import { chats, isGroupChat, type Chat, type Message, isBabbles } from '$lib/stores/chat' + import { chats, isGroupChat, type Chat, isBabbles, type ChatMessage } from '$lib/stores/chat' import ROUTES from '$lib/routes' import AuthenticatedOnly from '$lib/components/authenticated-only.svelte' @@ -43,7 +43,7 @@ return lastMessage ? lastMessage.timestamp : 0 } - function lastSenderName(chat: Chat, myMessage?: boolean, lastMessage?: Message) { + function lastSenderName(chat: Chat, myMessage?: boolean, lastMessage?: ChatMessage) { if (myMessage) { return 'You: ' } diff --git a/src/routes/chat/[id]/+page.svelte b/src/routes/chat/[id]/+page.svelte index ad430769..83d0b0cc 100644 --- a/src/routes/chat/[id]/+page.svelte +++ b/src/routes/chat/[id]/+page.svelte @@ -12,6 +12,7 @@ import Button from '$lib/components/button.svelte' import Avatar from '$lib/components/avatar.svelte' import WakuObject from '$lib/objects/chat.svelte' + import ChatObjectInvite from '$lib/components/chat-object-invite.svelte' import { goto } from '$app/navigation' import { chats, isGroupChat } from '$lib/stores/chat' @@ -173,6 +174,15 @@ {:else if message.type === 'data'} + {:else if message.type === 'install'} + {/if} {/each} diff --git a/src/routes/chat/[id]/object/new/+page.svelte b/src/routes/chat/[id]/object/new/+page.svelte index d3da93ee..f237c209 100644 --- a/src/routes/chat/[id]/object/new/+page.svelte +++ b/src/routes/chat/[id]/object/new/+page.svelte @@ -6,6 +6,7 @@ import Header from '$lib/components/header.svelte' import Button from '$lib/components/button.svelte' import Close from '$lib/components/icons/close.svelte' + import Modal from '$lib/components/modal.svelte' import { goto } from '$app/navigation' import { chats } from '$lib/stores/chat' @@ -16,26 +17,34 @@ import ButtonBlock from '$lib/components/button-block.svelte' import AuthenticatedOnly from '$lib/components/authenticated-only.svelte' import Layout from '$lib/components/layout.svelte' - import type { JSONSerializable } from '$lib/objects' + import type { JSONSerializable, WakuObjectSvelteDescriptor } from '$lib/objects' import { genRandomHex } from '$lib/utils' - import { getInstalledObjectList } from '$lib/objects/lookup' + import { getInstalledObjectList, type InstalledObjectDescriptor } from '$lib/objects/lookup' import { errorStore } from '$lib/stores/error' + import ChatLaunch from '$lib/components/icons/chat-launch.svelte' - const objects = getInstalledObjectList().map((object) => ({ - ...object, - onClick: object.standalone - ? () => { - goto(ROUTES.OBJECT($page.params.id, encodeURIComponent(object.objectId), 'new')) - } - : () => { - createObject(object.objectId, { - /* TODO empty */ - }) - goto(ROUTES.CHAT($page.params.id)) - }, - })) + $: otherUser = $chats.chats + .get($page.params.id) + ?.users.find((m) => m.publicKey !== $walletStore.wallet?.signingKey.compressedPublicKey) + + const installedObjects = getInstalledObjectList() + .map((object) => ({ + ...object, + add: () => addObject(object), + showInvite: () => (showInvite = object), + })) + .filter((object) => object.installed) + + const chatObjects = $chats.chats.get($page.params.id)?.objects + const alreadyUsedObjects = installedObjects.filter( + (object) => chatObjects && chatObjects.includes(object.objectId), + ) + const notUsedObjects = installedObjects.filter( + (object) => !chatObjects || !chatObjects.includes(object.objectId), + ) let loading = false let text = '' + let showInvite: InstalledObjectDescriptor | undefined = undefined const createObject = async (objectId: string, t: JSONSerializable) => { const instanceId = genRandomHex(12) @@ -67,12 +76,66 @@ loading = false } - $: otherUser = $chats.chats - .get($page.params.id) - ?.users.find((m) => m.publicKey !== $walletStore.wallet?.signingKey.compressedPublicKey) + function addObject(object: WakuObjectSvelteDescriptor) { + if (object.standalone) { + goto(ROUTES.OBJECT($page.params.id, encodeURIComponent(object.objectId), 'new')) + return + } + + createObject(object.objectId, { + /* TODO empty */ + }) + goto(ROUTES.CHAT($page.params.id)) + } + + async function sendInstallInvite(object: InstalledObjectDescriptor) { + // TODO temporary workaround for preinstalled objects + if (object.preInstalled) { + addObject(object) + return + } + + console.debug('send invite', { object }) + + const wallet = $walletStore.wallet + if (!wallet) { + errorStore.addEnd({ + title: 'Wallet Error', + message: 'No wallet found', + retry: () => sendInstallInvite(object), + reload: true, + }) + return + } + + try { + await adapters.sendInstall($page.params.id, object.objectId, 'invite') + showInvite = undefined + history.back() + } catch (error) { + errorStore.addEnd({ + title: 'Error', + message: `Failed to send invite. ${(error as Error)?.message}`, + retry: () => sendInstallInvite(object), + }) + } + } + {#if showInvite} + + + + + {/if} history.back()}> @@ -90,11 +153,31 @@ + + {#if alreadyUsedObjects.length > 0} +
Already in this chat
+ {/if} +
+ {#each alreadyUsedObjects as object} +
+ +
+ {/each} +
+ + {#if notUsedObjects.length > 0} +
Not yet in this chat
+ {/if}
- {#each objects as object} + {#each notUsedObjects as object}
diff --git a/src/routes/identity/+page.svelte b/src/routes/identity/+page.svelte index 9b285699..ed7da393 100644 --- a/src/routes/identity/+page.svelte +++ b/src/routes/identity/+page.svelte @@ -31,6 +31,7 @@ import { errorStore } from '$lib/stores/error' import { installedObjectStore } from '$lib/stores/installed-objects' import { getObjectSpec } from '$lib/objects/external/lib' + import DataBlob from '$lib/components/icons/data-blob.svelte' let avatar = $profile.avatar let name = $profile.name @@ -97,20 +98,6 @@ }, 1000) } - async function addObject() { - const { object } = await getObjectSpec(objectPath, 'chat') - installedObjectStore.update((state) => { - state.objects.set(objectPath, { - objectId: objectPath, - name: object.name, - description: object.description, - logo: object.files.logo.path, - }) - return { ...state } - }) - objectPath = '' - } - onDestroy(() => { if (timer) { clearTimeout(timer) @@ -162,7 +149,7 @@
- goto(routes.IDENTITY_PREFERENCES)}> + goto(routes.IDENTITY_PREFERENCES)}>
Preferences @@ -174,6 +161,18 @@
+ goto(routes.SETTINGS_OBJECTS)}> + +
+ Waku Objects +
+
+ +
+
+

If you disconnect or need to recover access to your identity you will need your recovery @@ -195,11 +194,7 @@ Disconnect identity from device - - - - - + diff --git a/src/routes/objects/+page.svelte b/src/routes/objects/+page.svelte new file mode 100644 index 00000000..bd594b85 --- /dev/null +++ b/src/routes/objects/+page.svelte @@ -0,0 +1,101 @@ + + +{#if loading} + + + + + +{:else} + + +

+ +
+ + + {#each installedObjects as object} + goto(routes.SETTINGS_OBJECT(hashString(object.objectId)))} + > + +
+ {object.name} + {object.name}{`${object.preInstalled ? '*' : ''}`} +
+
+ +
+
+
+ {/each} + + + + + +
+ +{/if} + + diff --git a/src/routes/objects/[object_id]/+page.svelte b/src/routes/objects/[object_id]/+page.svelte new file mode 100644 index 00000000..49ff6e57 --- /dev/null +++ b/src/routes/objects/[object_id]/+page.svelte @@ -0,0 +1,75 @@ + + +{#if loading || !object} + + + + + +{:else} + + +
+ +
+
+ + +

{object.name}

+

{object.description}

+
+ + {#if !object.preInstalled && object.installed} + + {/if} +
+
+{/if} + + From 256482fa82c7d6f1f48f3417ff38e7df3cd9816f Mon Sep 17 00:00:00 2001 From: Attila Gazso <230163+agazso@users.noreply.github.com> Date: Wed, 22 Nov 2023 15:16:15 +0100 Subject: [PATCH 2/5] feat: install invite flow --- src/lib/adapters/waku/index.ts | 14 ++--- src/lib/components/chat-message.svelte | 7 ++- src/lib/components/chat-object-invite.svelte | 61 +++++++++++++------ src/lib/components/icons/information.svelte | 23 +++++++ src/lib/components/object-install-info.svelte | 55 +++++++++++++++++ src/routes/chat/[id]/object/new/+page.svelte | 11 ---- src/routes/group/chat/[id]/+page.svelte | 24 ++++++-- src/routes/identity/+page.svelte | 3 - src/routes/objects/+page.svelte | 3 +- src/routes/objects/[object_id]/+page.svelte | 21 +++---- 10 files changed, 164 insertions(+), 58 deletions(-) create mode 100644 src/lib/components/icons/information.svelte create mode 100644 src/lib/components/object-install-info.svelte diff --git a/src/lib/adapters/waku/index.ts b/src/lib/adapters/waku/index.ts index 21fed29b..1cda9b57 100644 --- a/src/lib/adapters/waku/index.ts +++ b/src/lib/adapters/waku/index.ts @@ -231,6 +231,8 @@ async function executeOnInstallMessage( chatId: string, message: WithMeta, ) { + console.debug({ publicKey, message }) + if (message.senderPublicKey === publicKey) { if (message.command === 'accept') { const installedObjects = get(installedObjectStore).objects @@ -275,22 +277,20 @@ async function executeOnInstallMessage( objectId: message.objectId, name: objectSpec.object.name, description: objectSpec.object.description, - // TODO fix relative path to absolute logo: objectSpec.object.files.logo.path, installed: false, } installedObjectStore.addInstalledObject(installedObject) } else if (message.command === 'accept') { - const installedObjects = get(installedObjectStore).objects - if (!installedObjects.has(message.objectId)) { + const installedObject = get(installedObjectStore).objects.get(message.objectId) + if (!installedObject) { return } - installedObjectStore.updateInstalledObject(message.objectId, (object) => ({ - ...object, - installed: true, - })) + if (!installedObject.installed) { + return + } // add to chat objects chats.updateChat(chatId, (chat) => { diff --git a/src/lib/components/chat-message.svelte b/src/lib/components/chat-message.svelte index 935184b0..00c77c0c 100644 --- a/src/lib/components/chat-message.svelte +++ b/src/lib/components/chat-message.svelte @@ -77,12 +77,14 @@ max-width: 75%; margin-right: auto; margin-left: 0; + font-family: (--font-serif); &:not(:last-child) { margin-bottom: var(--spacing-12); } &.object { + font-family: sans-serif; .message-content { width: 100%; } @@ -93,7 +95,6 @@ padding: var(--spacing-12); border-radius: var(--border-radius); display: inline-block; - font-family: var(--font-serif); background-color: var(--color-base, var(--color-dark-step-40)); } @@ -118,6 +119,10 @@ } } + &.object { + font-style: normal; + } + .timestamp { text-align: end; } diff --git a/src/lib/components/chat-object-invite.svelte b/src/lib/components/chat-object-invite.svelte index 3dd8aa84..35de1bd4 100644 --- a/src/lib/components/chat-object-invite.svelte +++ b/src/lib/components/chat-object-invite.svelte @@ -1,5 +1,6 @@ - + {#if !object} {:else}
{#if message.command === 'invite'} - {senderName} invited {recipientName} to use "{object.name}" in this chat. + {senderNameLabel} invited {recipientName} to use "{object.name}" in this chat. {:else if message.command === 'accept'} - {senderName} accepted the invite. You can now use "{object.name}" in this chat. + {senderNameLabel} accepted the invite. You can now use "{object.name}" in this chat. {/if} - + object && goto(routes.SETTINGS_OBJECT(hashString(object.objectId)))} + name={object.name} + logoImg={object.logo} + logoAlt={`${object.name} logo`} + /> {#if message.command === 'invite'} - {#if myMessage} + {#if isInstalledInChat} +

+ Invite accepted +

+ {:else if myMessage}

- {#if isInstalledInChat} - Invite accepted - {:else} - Invite pending... - {/if} + Invite pending

{:else} - acceptInstall(message.objectId)} + > Accept {/if} {:else if message.command === 'accept'} @@ -75,20 +89,29 @@ {#if isInstalledInChat} Invite accepted {:else} - Invite pending... + Invite pending {/if}

{/if}
{/if} + + {#if $$slots.avatar} + + {/if} +
diff --git a/src/lib/components/icons/information.svelte b/src/lib/components/icons/information.svelte new file mode 100644 index 00000000..4a5149ea --- /dev/null +++ b/src/lib/components/icons/information.svelte @@ -0,0 +1,23 @@ + + + + + + + + + + + diff --git a/src/lib/components/object-install-info.svelte b/src/lib/components/object-install-info.svelte new file mode 100644 index 00000000..1215dde8 --- /dev/null +++ b/src/lib/components/object-install-info.svelte @@ -0,0 +1,55 @@ + + +
+ {#if logoImg} + {logoAlt} + {/if} + {#if name} +

{name}

+ {/if} +
+ +
+ +
+ + diff --git a/src/routes/chat/[id]/object/new/+page.svelte b/src/routes/chat/[id]/object/new/+page.svelte index f237c209..1291637b 100644 --- a/src/routes/chat/[id]/object/new/+page.svelte +++ b/src/routes/chat/[id]/object/new/+page.svelte @@ -97,17 +97,6 @@ console.debug('send invite', { object }) - const wallet = $walletStore.wallet - if (!wallet) { - errorStore.addEnd({ - title: 'Wallet Error', - message: 'No wallet found', - retry: () => sendInstallInvite(object), - reload: true, - }) - return - } - try { await adapters.sendInstall($page.params.id, object.objectId, 'invite') showInvite = undefined diff --git a/src/routes/group/chat/[id]/+page.svelte b/src/routes/group/chat/[id]/+page.svelte index 388acaba..dacc5b51 100644 --- a/src/routes/group/chat/[id]/+page.svelte +++ b/src/routes/group/chat/[id]/+page.svelte @@ -38,6 +38,7 @@ import ChatDateBadge from '$lib/components/chat-date-badge.svelte' import { errorStore } from '$lib/stores/error' import { publicKeyToAddress } from '$lib/adapters/waku/crypto' + import ChatObjectInvite from '$lib/components/chat-object-invite.svelte' let div: HTMLElement let autoscroll = true @@ -197,12 +198,14 @@
{:else} + {@const publicKey = wallet.signingKey.compressedPublicKey}
{#each messages as message, i} + {@const sender = chat.users.find((u) => message.senderPublicKey === u.publicKey)} {#if message.type === 'user' && message.text?.length > 0} {@const sameSender = messages[i].senderPublicKey === messages[i - 1]?.senderPublicKey} @@ -210,10 +213,6 @@ i + 1 === messages.length || messages[i].senderPublicKey !== messages[i + 1]?.senderPublicKey || messages[i + 1]?.type !== 'user'} - {@const sender = chat.users.find( - (u) => message.senderPublicKey === u.publicKey, - )} - {@const publicKey = wallet.signingKey.compressedPublicKey} {#if i === 0 || (i > 0 && areDifferentDays(messages[i].timestamp, messages[i - 1].timestamp))} {/if} @@ -238,6 +237,23 @@ {:else if message.type === 'data'} + {:else if message.type === 'install'} + + + {#if message.senderPublicKey !== publicKey} + + {/if} + + {/if} {/each}
diff --git a/src/routes/identity/+page.svelte b/src/routes/identity/+page.svelte index ed7da393..165b7aaa 100644 --- a/src/routes/identity/+page.svelte +++ b/src/routes/identity/+page.svelte @@ -29,13 +29,10 @@ import { uploadPicture } from '$lib/adapters/ipfs' import Avatar from '$lib/components/avatar.svelte' import { errorStore } from '$lib/stores/error' - import { installedObjectStore } from '$lib/stores/installed-objects' - import { getObjectSpec } from '$lib/objects/external/lib' import DataBlob from '$lib/components/icons/data-blob.svelte' let avatar = $profile.avatar let name = $profile.name - let objectPath = '' $: if ($profile.loading === false && !name && !avatar) { name = $profile.name diff --git a/src/routes/objects/+page.svelte b/src/routes/objects/+page.svelte index bd594b85..7da99e7b 100644 --- a/src/routes/objects/+page.svelte +++ b/src/routes/objects/+page.svelte @@ -22,7 +22,7 @@ let objectPath = '' $: loading = $installedObjectStore.loading - $: installedObjects = loading ? [] : getInstalledObjectList() + $: installedObjects = $installedObjectStore && getInstalledObjectList() async function addObject() { const { object } = await getObjectSpec(objectPath, 'chat') @@ -75,6 +75,7 @@ {/each} + Developer stuff diff --git a/src/routes/objects/[object_id]/+page.svelte b/src/routes/objects/[object_id]/+page.svelte index 49ff6e57..8e807276 100644 --- a/src/routes/objects/[object_id]/+page.svelte +++ b/src/routes/objects/[object_id]/+page.svelte @@ -13,6 +13,7 @@ import { page } from '$app/stores' import { getInstalledObjectList } from '$lib/objects/lookup' import { hashString } from '$lib/adapters/waku/crypto' + import TrashCan from '$lib/components/icons/trash-can.svelte' let hashedObjectId = $page.params.object_id @@ -20,8 +21,6 @@ $: objects = loading ? undefined : getInstalledObjectList() $: object = objects?.find((object) => hashString(object.objectId) === hashedObjectId) - $: console.debug({ objects, object }) - function uninstall(objectId: string) { // TODO are you sure dialog installedObjectStore.removeInstalledObject(objectId) @@ -46,12 +45,13 @@ + {object.name}

{object.name}

{object.description}

{#if !object.preInstalled && object.installed} - + {/if}
@@ -59,17 +59,14 @@ From ee15795ca7a74c6d1bc54e55067820e5b5acbfc7 Mon Sep 17 00:00:00 2001 From: Attila Gazso <230163+agazso@users.noreply.github.com> Date: Wed, 22 Nov 2023 15:23:38 +0100 Subject: [PATCH 3/5] chore: cleanup --- src/routes/chat/[id]/object/new/+page.svelte | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/routes/chat/[id]/object/new/+page.svelte b/src/routes/chat/[id]/object/new/+page.svelte index 1291637b..76f0ec08 100644 --- a/src/routes/chat/[id]/object/new/+page.svelte +++ b/src/routes/chat/[id]/object/new/+page.svelte @@ -89,14 +89,12 @@ } async function sendInstallInvite(object: InstalledObjectDescriptor) { - // TODO temporary workaround for preinstalled objects + // workaround for preinstalled objects if (object.preInstalled) { addObject(object) return } - console.debug('send invite', { object }) - try { await adapters.sendInstall($page.params.id, object.objectId, 'invite') showInvite = undefined From 493295e6d1d10b86d3a8f0a8e6e652b28787763d Mon Sep 17 00:00:00 2001 From: Attila Gazso <230163+agazso@users.noreply.github.com> Date: Wed, 22 Nov 2023 15:36:52 +0100 Subject: [PATCH 4/5] feat: mark preinstalled objects with asterisk --- src/routes/chat/[id]/object/new/+page.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/chat/[id]/object/new/+page.svelte b/src/routes/chat/[id]/object/new/+page.svelte index 76f0ec08..faf97ed9 100644 --- a/src/routes/chat/[id]/object/new/+page.svelte +++ b/src/routes/chat/[id]/object/new/+page.svelte @@ -150,7 +150,7 @@
@@ -166,7 +166,7 @@
From 9ebc7cdbc2b39df4dadfe245955200fdd08fcbe2 Mon Sep 17 00:00:00 2001 From: Attila Gazso <230163+agazso@users.noreply.github.com> Date: Wed, 22 Nov 2023 15:45:44 +0100 Subject: [PATCH 5/5] chore: cleanup --- src/lib/adapters/waku/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/adapters/waku/index.ts b/src/lib/adapters/waku/index.ts index 1cda9b57..182f20d8 100644 --- a/src/lib/adapters/waku/index.ts +++ b/src/lib/adapters/waku/index.ts @@ -231,8 +231,6 @@ async function executeOnInstallMessage( chatId: string, message: WithMeta, ) { - console.debug({ publicKey, message }) - if (message.senderPublicKey === publicKey) { if (message.command === 'accept') { const installedObjects = get(installedObjectStore).objects