@novasamatech/host-chat 0.8.0-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -0
- package/dist/accountService.d.ts +37 -0
- package/dist/accountService.js +84 -0
- package/dist/codec/attachment.d.ts +108 -0
- package/dist/codec/attachment.js +28 -0
- package/dist/codec/attachment.spec.d.ts +1 -0
- package/dist/codec/attachment.spec.js +91 -0
- package/dist/codec/contact.d.ts +9 -0
- package/dist/codec/contact.js +11 -0
- package/dist/codec/localMessage.d.ts +235 -0
- package/dist/codec/localMessage.js +19 -0
- package/dist/codec/message.d.ts +767 -0
- package/dist/codec/message.js +103 -0
- package/dist/helpers.d.ts +1 -0
- package/dist/helpers.js +9 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/messagesRepository.d.ts +9 -0
- package/dist/messagesRepository.js +1 -0
- package/dist/session.d.ts +25 -0
- package/dist/session.js +99 -0
- package/package.json +52 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Enum, Hex, Nullable, Status } from '@novasamatech/scale';
|
|
2
|
+
import { Bytes, Option, Struct, Vector, _void, compact, str, u64 } from 'scale-ts';
|
|
3
|
+
import { FileVariant } from './attachment.js';
|
|
4
|
+
const AccountIdCodec = Bytes(32);
|
|
5
|
+
const PublicKeyCodec = Bytes(65);
|
|
6
|
+
export const TextContent = str;
|
|
7
|
+
export const RichTextContent = Struct({
|
|
8
|
+
text: Option(TextContent),
|
|
9
|
+
attachments: Option(Vector(FileVariant)),
|
|
10
|
+
});
|
|
11
|
+
export const Platform = Status('Android', 'iOS');
|
|
12
|
+
export const TokenContent = Struct({
|
|
13
|
+
token: Hex(),
|
|
14
|
+
platform: Platform,
|
|
15
|
+
});
|
|
16
|
+
export const SendContent = Struct({
|
|
17
|
+
amount: compact,
|
|
18
|
+
assetId: Nullable(str), // 0x scale encoded assetId or number, null for native
|
|
19
|
+
blockHash: Hex(32), // fixed 32 byte array
|
|
20
|
+
extrinsicHash: Hex(32), // fixed 32 byte array
|
|
21
|
+
});
|
|
22
|
+
export const ReactionContent = Struct({
|
|
23
|
+
messageId: str, // encoded as string
|
|
24
|
+
emoji: str,
|
|
25
|
+
});
|
|
26
|
+
export const ReplyContent = Struct({
|
|
27
|
+
messageId: str,
|
|
28
|
+
ownContent: RichTextContent,
|
|
29
|
+
});
|
|
30
|
+
export const EditContent = Struct({
|
|
31
|
+
messageId: str,
|
|
32
|
+
newContent: RichTextContent,
|
|
33
|
+
});
|
|
34
|
+
// V2 multi-device roster mutations carried as chat-content variants.
|
|
35
|
+
// Android's `ChatMessageStatementContent.DeviceAdded`/`DeviceRemoved` use
|
|
36
|
+
// the `AccountId` / `EncodedPublicKey` wrapper types without `@FixedLength`,
|
|
37
|
+
// so substrate-sdk-android falls through to length-prefixed `Vec<u8>` on the
|
|
38
|
+
// wire. We accept that shape here (`Bytes()` = compact-length-prefixed bytes)
|
|
39
|
+
// instead of fixed-size to stay tolerant of Android's emitter. Other variants
|
|
40
|
+
// that use `@FixedLength` on raw `ByteArray` (e.g. `DeviceInfoContent` below)
|
|
41
|
+
// stay fixed-size and continue to use AccountIdCodec/PublicKeyCodec.
|
|
42
|
+
export const DeviceAddedContent = Struct({
|
|
43
|
+
statementAccountId: Bytes(),
|
|
44
|
+
encryptionPublicKey: Bytes(),
|
|
45
|
+
});
|
|
46
|
+
export const DeviceRemovedContent = Struct({
|
|
47
|
+
statementAccountId: Bytes(),
|
|
48
|
+
});
|
|
49
|
+
// Legacy single-device accept (index 14, iOS V1). Wire: bare `String`.
|
|
50
|
+
// Superseded by `deviceChatAccepted` (index 20); keep for backward decode.
|
|
51
|
+
export const ChatAcceptedContent = Struct({
|
|
52
|
+
messageId: str,
|
|
53
|
+
});
|
|
54
|
+
// Per-device descriptor for the multi-device accept. Matches iOS `Chat.PeerDevice`
|
|
55
|
+
// and Android `DeviceInfoScale`.
|
|
56
|
+
export const DeviceInfoContent = Struct({
|
|
57
|
+
statementAccountId: AccountIdCodec,
|
|
58
|
+
encryptionPublicKey: PublicKeyCodec,
|
|
59
|
+
});
|
|
60
|
+
// Multi-device accept (index 20, per chat spec v0.1
|
|
61
|
+
// https://hackmd.io/@1JCaGppGSUqHtJilikYaKw/Ski9naYdWe):
|
|
62
|
+
// DeviceChatAccepted = { requestId: String, device: DeviceInfo }
|
|
63
|
+
// Sent via identity-level session SessionId(B, A) encrypted with K(A,B);
|
|
64
|
+
// identity-level encryption lets all of A's devices decrypt without per-device
|
|
65
|
+
// envelope. Index 19 is reserved (placeholder slot between deviceRemoved=18
|
|
66
|
+
// and deviceChatAccepted=20).
|
|
67
|
+
export const DeviceChatAcceptedContent = Struct({
|
|
68
|
+
requestId: str,
|
|
69
|
+
device: DeviceInfoContent,
|
|
70
|
+
});
|
|
71
|
+
// Note: enum indices MUST match iOS/Android SCALE codecs.
|
|
72
|
+
// Indices are auto-assigned sequentially, so order matters.
|
|
73
|
+
export const MessageContent = Enum({
|
|
74
|
+
text: TextContent, // 0
|
|
75
|
+
token: TokenContent, // 1
|
|
76
|
+
send: SendContent, // 2
|
|
77
|
+
contactAdded: _void, // 3
|
|
78
|
+
reacted: ReactionContent, // 4
|
|
79
|
+
reactionRemoved: ReactionContent, // 5
|
|
80
|
+
_reserved6: _void, // 6 — reserved (unused)
|
|
81
|
+
reply: ReplyContent, // 7
|
|
82
|
+
dataChannelOffer: _void, // 8
|
|
83
|
+
dataChannelAnswer: _void, // 9
|
|
84
|
+
dataChannelCandidates: _void, // 10
|
|
85
|
+
_reserved11: _void, // 11 — reserved (unused)
|
|
86
|
+
edit: EditContent, // 12
|
|
87
|
+
leftChat: _void, // 13
|
|
88
|
+
chatAccepted: ChatAcceptedContent, // 14 (legacy single-device accept, iOS V1)
|
|
89
|
+
richText: RichTextContent, // 15
|
|
90
|
+
_reserved16: _void, // 16 — reserved (android `coinagePayment`, unused on desktop)
|
|
91
|
+
deviceAdded: DeviceAddedContent, // 17
|
|
92
|
+
deviceRemoved: DeviceRemovedContent, // 18
|
|
93
|
+
_reserved19: _void, // 19 — reserved (placeholder so deviceChatAccepted lands on the spec'd index 20)
|
|
94
|
+
deviceChatAccepted: DeviceChatAcceptedContent, // 20 (multi-device accept, spec v0.1)
|
|
95
|
+
});
|
|
96
|
+
export const VersionedMessageContent = Enum({
|
|
97
|
+
v1: MessageContent,
|
|
98
|
+
});
|
|
99
|
+
export const ChatMessage = Struct({
|
|
100
|
+
messageId: str,
|
|
101
|
+
timestamp: u64,
|
|
102
|
+
versioned: VersionedMessageContent,
|
|
103
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function toError(err: unknown): Error;
|
package/dist/helpers.js
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createAccountService } from './accountService.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createAccountService } from './accountService.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ResultAsync } from 'neverthrow';
|
|
2
|
+
import type { CodecType } from 'scale-ts';
|
|
3
|
+
import type { LocalMessage } from './codec/localMessage.js';
|
|
4
|
+
export type MessagesRepository = {
|
|
5
|
+
save(message: CodecType<typeof LocalMessage>): ResultAsync<void, Error>;
|
|
6
|
+
update(message: CodecType<typeof LocalMessage>): ResultAsync<void, Error>;
|
|
7
|
+
read(): ResultAsync<CodecType<typeof LocalMessage>[], Error>;
|
|
8
|
+
clear(): ResultAsync<void, Error>;
|
|
9
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { ResultAsync } from 'neverthrow';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CodecType } from 'scale-ts';
|
|
2
|
+
import type { LocalMessage } from './codec/localMessage.js';
|
|
3
|
+
import type { MessageContent } from './codec/message.js';
|
|
4
|
+
export type ChatSession = {
|
|
5
|
+
/**
|
|
6
|
+
* Send a message to the peer
|
|
7
|
+
* @param content Message content
|
|
8
|
+
* @returns Message ID
|
|
9
|
+
*/
|
|
10
|
+
send(content: CodecType<typeof MessageContent>): Promise<{
|
|
11
|
+
messageId: string;
|
|
12
|
+
}>;
|
|
13
|
+
markAsRead: (messages: string[]) => void;
|
|
14
|
+
onMessage(callback: (message: CodecType<typeof LocalMessage>) => void): void;
|
|
15
|
+
/**
|
|
16
|
+
* Leave the chat session.
|
|
17
|
+
* This will send a leave message to the peer, remove the session from the storage, and unsubscribe from the messages.
|
|
18
|
+
*/
|
|
19
|
+
leave(): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Dispose session
|
|
22
|
+
*/
|
|
23
|
+
close: () => void;
|
|
24
|
+
};
|
|
25
|
+
export declare function createChatSession(): ChatSession;
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { nanoid } from 'nanoid';
|
|
2
|
+
// type Params = {
|
|
3
|
+
// localAccount: LocalSessionAccount;
|
|
4
|
+
// remoteAccount: RemoteSessionAccount;
|
|
5
|
+
// statementStore: StatementStoreAdapter;
|
|
6
|
+
// messageRepository: MessagesRepository;
|
|
7
|
+
// secret: Uint8Array;
|
|
8
|
+
// };
|
|
9
|
+
export function createChatSession() {
|
|
10
|
+
// const prover = createSr25519Prover(secret);
|
|
11
|
+
// const encryption = createEncryption(remoteAccount.publicKey);
|
|
12
|
+
// const session = createSession({ localAccount, remoteAccount, prover, encryption, statementStore });
|
|
13
|
+
//
|
|
14
|
+
// const unsubscribe = session.subscribe(ChatMessageCodec, async messages => {
|
|
15
|
+
// messages.forEach(message => {
|
|
16
|
+
// if (message.type !== 'request') return;
|
|
17
|
+
// const localMessage: CodecType<typeof LocalMessage> = {
|
|
18
|
+
// remote:
|
|
19
|
+
// message.payload.status === 'parsed'
|
|
20
|
+
// ? enumValue('message', message.payload.value)
|
|
21
|
+
// : enumValue('unsupported', message.payload.value),
|
|
22
|
+
// peerId: remoteAccount.accountId,
|
|
23
|
+
// status: enumValue('incoming', 'new'),
|
|
24
|
+
// order: BigInt(Date.now()),
|
|
25
|
+
// };
|
|
26
|
+
//
|
|
27
|
+
// onMessage(localMessage);
|
|
28
|
+
// });
|
|
29
|
+
// });
|
|
30
|
+
//
|
|
31
|
+
// const generateMessageId = () => nanoid(12);
|
|
32
|
+
//
|
|
33
|
+
// const sendMessage = async (content: CodecType<typeof MessageContent>): Promise<string> => {
|
|
34
|
+
// const messageId = generateMessageId();
|
|
35
|
+
// const timestamp = BigInt(Date.now());
|
|
36
|
+
//
|
|
37
|
+
// const chatMessage: CodecType<typeof ChatMessage> = {
|
|
38
|
+
// messageId,
|
|
39
|
+
// timestamp,
|
|
40
|
+
// versioned: {
|
|
41
|
+
// tag: 'v1',
|
|
42
|
+
// value: content,
|
|
43
|
+
// },
|
|
44
|
+
// };
|
|
45
|
+
//
|
|
46
|
+
// await session.request(ChatMessageCodec, chatMessage);
|
|
47
|
+
//
|
|
48
|
+
// // Notify status update callback if provided
|
|
49
|
+
// if (onStatusUpdate) {
|
|
50
|
+
// await onStatusUpdate(messageId, 'sent');
|
|
51
|
+
// }
|
|
52
|
+
//
|
|
53
|
+
// return messageId;
|
|
54
|
+
// };
|
|
55
|
+
//
|
|
56
|
+
// // Update message status
|
|
57
|
+
// const updateMessageStatus = async (
|
|
58
|
+
// messageId: string,
|
|
59
|
+
// status: CodecType<typeof OutgoingStatus | typeof IncomingStatus>,
|
|
60
|
+
// ): Promise<void> => {
|
|
61
|
+
// if (onStatusUpdate) {
|
|
62
|
+
// await onStatusUpdate(messageId, status);
|
|
63
|
+
// }
|
|
64
|
+
// };
|
|
65
|
+
//
|
|
66
|
+
// // Get peer ID
|
|
67
|
+
// const getPeerId = (): Uint8Array => {
|
|
68
|
+
// return remoteAccount.accountId;
|
|
69
|
+
// };
|
|
70
|
+
//
|
|
71
|
+
// // Close session
|
|
72
|
+
// const close = (): void => {
|
|
73
|
+
// unsubscribe();
|
|
74
|
+
// };
|
|
75
|
+
//
|
|
76
|
+
// // Auto-initialize if callback provided
|
|
77
|
+
// if (onMessage) {
|
|
78
|
+
// initializeSubscription();
|
|
79
|
+
// }
|
|
80
|
+
return {
|
|
81
|
+
send() {
|
|
82
|
+
return Promise.resolve({ messageId: nanoid(12) });
|
|
83
|
+
},
|
|
84
|
+
markAsRead() {
|
|
85
|
+
/* empty */
|
|
86
|
+
},
|
|
87
|
+
onMessage() {
|
|
88
|
+
return () => {
|
|
89
|
+
/* empty */
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
leave() {
|
|
93
|
+
return Promise.resolve();
|
|
94
|
+
},
|
|
95
|
+
close() {
|
|
96
|
+
/* empty */
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@novasamatech/host-chat",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.8.0-1",
|
|
5
|
+
"description": "Host statement store chat integration",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/paritytech/triangle-js-sdks.git"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"polkadot"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"papi:update": "papi update"
|
|
16
|
+
},
|
|
17
|
+
"main": "dist/index.js",
|
|
18
|
+
"exports": {
|
|
19
|
+
"./package.json": "./package.json",
|
|
20
|
+
".": {
|
|
21
|
+
"#/source": "./src/index.ts",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./codec/message": {
|
|
26
|
+
"types": "./dist/codec/message.d.ts",
|
|
27
|
+
"default": "./dist/codec/message.js"
|
|
28
|
+
},
|
|
29
|
+
"./codec/attachment": {
|
|
30
|
+
"types": "./dist/codec/attachment.d.ts",
|
|
31
|
+
"default": "./dist/codec/attachment.js"
|
|
32
|
+
},
|
|
33
|
+
"./session": {
|
|
34
|
+
"types": "./dist/session.d.ts",
|
|
35
|
+
"default": "./dist/session.js"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"README.md"
|
|
41
|
+
],
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@novasamatech/scale": "0.8.0-1",
|
|
44
|
+
"@novasamatech/statement-store": "0.8.0-1",
|
|
45
|
+
"@novasamatech/storage-adapter": "0.8.0-1",
|
|
46
|
+
"nanoid": "5.1.9",
|
|
47
|
+
"neverthrow": "^8.2.0"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
}
|
|
52
|
+
}
|