Skip to content

Commit ea43c35

Browse files
authored
fix(host-chat): drop private flag so the package is publishable (#180)
1 parent 802e844 commit ea43c35

3 files changed

Lines changed: 75 additions & 265 deletions

File tree

packages/host-chat/README.md

Lines changed: 68 additions & 263 deletions
Original file line numberDiff line numberDiff line change
@@ -1,297 +1,102 @@
1-
# @novasamatech/host-container
1+
# @novasamatech/host-chat
22

3-
A robust solution for hosting and managing decentralized applications (dapps) within the Polkadot ecosystem.
3+
Account lookup and chat-message codecs for host applications integrating with the Polkadot People chain.
44

55
## Overview
66

7-
Host container provides the infrastructure layer for securely embedding and communicating with third-party dapps.
8-
It handles the isolation boundary, message routing, lifecycle management, and security concerns inherent in hosting untrusted web content.
7+
`@novasamatech/host-chat` exposes the read side of the chat domain: discovering Polkadot
8+
accounts by username and resolving their on-chain identity from `Resources.Consumers`. It
9+
also publishes the SCALE codecs used by the chat wire protocol (messages, attachments,
10+
local-message envelopes) so host applications can decode statements they receive over the
11+
statement store.
12+
13+
The package is UI-framework agnostic. The main entry point returns plain async functions
14+
backed by [`neverthrow`](https://github.com/supermacro/neverthrow) `ResultAsync`, and the
15+
codec exports are pure SCALE codecs with no runtime side effects.
916

1017
## Installation
1118

1219
```shell
13-
npm install @novasamatech/host-container --save -E
14-
```
15-
16-
### Basic Container Setup
17-
18-
```ts
19-
import { createContainer, createIframeProvider } from '@novasamatech/host-container';
20-
21-
const iframe = document.createElement('iframe');
22-
23-
const provider = createIframeProvider({
24-
iframe,
25-
url: 'https://dapp.example.com'
26-
});
27-
const container = createContainer(provider);
28-
29-
document.body.appendChild(iframe);
30-
```
31-
32-
## API reference
33-
34-
### handleFeature
35-
36-
```ts
37-
container.handleFeature((params, { ok, err }) => {
38-
if (params.tag === 'Chat') {
39-
return ok(supportedChains.has(params.value));
40-
}
41-
return ok(false);
42-
});
43-
```
44-
45-
### handlePermissionRequest
46-
47-
```ts
48-
container.handlePermissionRequest(async (params, { ok, err }) => {
49-
if (params.tag === 'ChainConnect') {
50-
// Show permission dialog to user
51-
const approved = await showPermissionDialog(params.value);
52-
return approved ? ok(undefined) : err({ tag: 'Rejected' });
53-
}
54-
return err({ tag: 'Unknown', value: { reason: 'Unsupported permission type' } });
55-
});
56-
```
57-
58-
### handleStorageRead
59-
60-
```ts
61-
container.handleStorageRead(async (key, { ok, err }) => {
62-
const value = await storage.get(key);
63-
return ok(value ?? null);
64-
});
65-
```
66-
67-
### handleStorageWrite
68-
69-
```ts
70-
container.handleStorageWrite(async ([key, value], { ok, err }) => {
71-
try {
72-
await storage.set(key, value);
73-
return ok(undefined);
74-
} catch (e) {
75-
return err({ tag: 'Full' });
76-
}
77-
});
78-
```
79-
80-
### handleStorageClear
81-
82-
```ts
83-
container.handleStorageClear(async (key, { ok, err }) => {
84-
await storage.delete(key);
85-
return ok(undefined);
86-
});
87-
```
88-
89-
### handleAccountGet
90-
91-
```ts
92-
container.handleAccountGet(async ([dotnsId, derivationIndex], { ok, err }) => {
93-
const account = await getProductAccount(dotnsId, derivationIndex);
94-
if (account) {
95-
return ok({ publicKey: account.publicKey, name: account.name ?? null });
96-
}
97-
return err({ tag: 'NotConnected' });
98-
});
99-
```
100-
101-
### handleAccountGetAlias
102-
103-
```ts
104-
container.handleAccountGetAlias(async ([dotnsId, derivationIndex], { ok, err }) => {
105-
const alias = await getAccountAlias(dotnsId, derivationIndex);
106-
if (alias) {
107-
return ok({ context: alias.context, alias: alias.alias });
108-
}
109-
return err(new RequestCredentialsErr.NotConnected());
110-
});
111-
```
112-
113-
### handleAccountCreateProof
114-
115-
```ts
116-
container.handleAccountCreateProof(async ([[dotnsId, derivationIndex], ringLocation, message], { ok, err }) => {
117-
try {
118-
const proof = await createRingProof(dotnsId, derivationIndex, ringLocation, message);
119-
return ok(proof);
120-
} catch (e) {
121-
return err({ tag: 'RingNotFound' });
122-
}
123-
});
124-
```
125-
126-
### handleGetLegacyAccounts
127-
128-
```ts
129-
container.handleGetLegacyAccounts(async (_, { ok, err }) => {
130-
const accounts = await getLegacyAccounts();
131-
return ok(accounts);
132-
});
133-
```
134-
135-
### handleCreateTransaction
136-
137-
```ts
138-
container.handleCreateTransaction(async ([productAccountId, payload], { ok, err }) => {
139-
try {
140-
const signedTx = await createTransaction(productAccountId, payload);
141-
return ok(signedTx);
142-
} catch (e) {
143-
return err({ tag: 'Rejected' });
144-
}
145-
});
146-
```
147-
148-
### handleCreateTransactionWithLegacyAccount
149-
150-
```ts
151-
container.handleCreateTransactionWithLegacyAccount(async (payload, { ok, err }) => {
152-
try {
153-
const signedTx = await createTransactionWithLegacyAccount(payload);
154-
return ok(signedTx);
155-
} catch (e) {
156-
return err({ tag: 'Rejected' });
157-
}
158-
});
159-
```
160-
161-
### handleSignRaw
162-
163-
```ts
164-
container.handleSignRaw(async (payload, { ok, err }) => {
165-
try {
166-
const result = await signRaw(payload);
167-
return ok({ signature: result.signature, signedTransaction: result.signedTransaction });
168-
} catch (e) {
169-
return err({ tag: 'Rejected' });
170-
}
171-
});
172-
```
173-
174-
### handleSignPayload
175-
176-
```ts
177-
container.handleSignPayload(async (payload, { ok, err }) => {
178-
try {
179-
const result = await signPayload(payload);
180-
return ok({ signature: result.signature, signedTransaction: result.signedTransaction ?? null });
181-
} catch (e) {
182-
return err({ tag: 'Rejected' });
183-
}
184-
});
185-
```
186-
187-
### handleChatCreateContact
188-
189-
```ts
190-
container.handleChatCreateContact(async (contact, { ok, err }) => {
191-
await chatService.registerContact(contact);
192-
return ok(undefined);
193-
});
20+
npm install @novasamatech/host-chat --save -E
19421
```
19522

196-
### handleChatPostMessage
23+
## Getting started
19724

19825
```ts
199-
container.handleChatPostMessage(async (message, { ok, err }) => {
200-
const messageId = await chatService.postMessage(message);
201-
return ok({ messageId });
202-
});
203-
```
204-
205-
### handleChatActionSubscribe
26+
import { createAccountService } from '@novasamatech/host-chat';
27+
import { createLazyClient } from '@novasamatech/statement-store';
20628

207-
```ts
208-
container.handleChatActionSubscribe((_, send, interrupt) => {
209-
const listener = (action) => send(action);
210-
chatService.on('action', listener);
211-
return () => chatService.off('action', listener);
212-
});
213-
```
29+
const lazyClient = createLazyClient(/* chain provider */);
30+
const accounts = createAccountService('paseo-next-v2', lazyClient);
21431

215-
### handleStatementStoreCreateProof
216-
217-
```ts
218-
container.handleStatementStoreCreateProof(async ([[dotnsId, derivationIndex], statement], { ok, err }) => {
219-
try {
220-
const proof = await createStatementProof(dotnsId, derivationIndex, statement);
221-
return ok(proof);
222-
} catch (e) {
223-
return err({ tag: 'UnableToSign' });
32+
// Search the off-chain username index for accounts whose username starts with `alice`.
33+
const search = await accounts.search('alice', 'ASSIGNED');
34+
if (search.isOk()) {
35+
for (const hit of search.value) {
36+
console.log(hit.candidateAccountId, hit.username);
22437
}
225-
});
226-
```
227-
228-
### handleJsonRpcMessageSubscribe
229-
230-
```ts
231-
import { getWsProvider } from 'polkadot-api/ws-provider';
232-
233-
const provider = getWsProvider('wss://rpc.polkadot.io');
234-
container.handleJsonRpcMessageSubscribe(
235-
{ genesisHash: '0x...' },
236-
provider
237-
);
238-
```
239-
240-
### isReady
38+
}
24139

242-
```ts
243-
const ready = await container.isReady();
244-
if (ready) {
245-
console.log('Container is ready');
40+
// Resolve a specific account's on-chain identity.
41+
const identity = await accounts.getConsumerInfo('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY');
42+
if (identity.isOk() && identity.value) {
43+
console.log(identity.value.fullUsername, identity.value.credibility);
24644
}
24745
```
24846

249-
### dispose
47+
### Networks
25048

251-
```ts
252-
container.dispose();
253-
```
49+
`createAccountService` accepts one of:
25450

255-
### subscribeConnectionStatus
51+
| Network | People chain endpoint |
52+
| ---------------- | -------------------------------------- |
53+
| `stable` | Polkadot People |
54+
| `preview` | Westend People |
55+
| `paseo-next` | Paseo People (V1) |
56+
| `paseo-next-v2` | Paseo People (V2 multi-device) |
25657

257-
```ts
258-
const unsubscribe = container.subscribeConnectionStatus((status) => {
259-
console.log('Connection status:', status);
260-
});
261-
```
58+
Each network entry pins both the People chain WebSocket URL (used via `lazyClient`) and
59+
the off-chain identity-backend REST endpoint that `search` queries.
26260

263-
## PAPI provider support
61+
## API
26462

265-
Host container supports [PAPI](https://papi.how/) request redirection from product to host container.
266-
It can be useful to deduplicate socket connections or light client instances between multiple dapps.
63+
### `createAccountService(network, lazyClient)`
26764

268-
To support this feature, you should add two additional handlers to the container:
65+
Returns an object with two methods:
26966

270-
### Chain support check
271-
```ts
272-
const genesisHash = '0x...';
67+
- **`search(query, status)`** — query the off-chain username index. `status` is
68+
`'ASSIGNED' | 'PENDING'`. Resolves to a list of `{ candidateAccountId, username, status,
69+
onchainData, createdAt, updatedAt }` rows.
70+
- **`getConsumerInfo(address)`** — resolve a single SS58 address to an `Identity`
71+
(`{ accountId, fullUsername, liteUsername, credibility }`) by reading
72+
`Resources.Consumers` from the People chain. Returns `null` if the account has no
73+
consumer entry. Tolerates both snake_case (V1) and camelCase (V2) runtime field names.
27374

274-
container.handleFeature(async (feature) => {
275-
return feature.tag === 'Chain' && feature.value === genesisHash;
276-
});
277-
```
75+
Both methods return `ResultAsync<…, Error>`; call `.isOk()` / `.isErr()` to discriminate.
76+
77+
## Codec subpath exports
27878

279-
### Provider implementation
79+
The chat wire codecs are exposed under explicit subpaths so they can be tree-shaken
80+
independently of the main entry point:
28081

28182
```ts
282-
import { getWsProvider } from 'polkadot-api/ws-provider';
83+
import {
84+
ChatMessage,
85+
TextContent,
86+
RichTextContent,
87+
ChatAcceptedContent,
88+
DeviceAddedContent,
89+
DeviceRemovedContent,
90+
} from '@novasamatech/host-chat/codec/message';
28391

284-
const genesisHash = '0x...';
285-
const provider = getWsProvider('wss://...');
92+
import {
93+
FileMeta,
94+
FileVariant,
95+
P2PMixnetFile,
96+
} from '@novasamatech/host-chat/codec/attachment';
28697

287-
container.connectToPapiProvider(genesisHash, provider);
98+
import type { ChatSession } from '@novasamatech/host-chat/session';
28899
```
289100

290-
## Known pitfalls
291-
292-
### CSP error on iframe loading
293-
If a dapp is hosted on a different domain than the container and uses HTTPS, you should add this meta tag to your host application HTML:
294-
295-
```html
296-
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
297-
```
101+
These are byte-compatible with the Android / iOS Polkadot Mobile clients — modify with
102+
care, the indices are pinned by the protocol.

packages/host-chat/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
"name": "@novasamatech/host-chat",
33
"type": "module",
44
"version": "0.8.0-0",
5-
"private": "true",
65
"description": "Host statement store chat integration",
76
"license": "Apache-2.0",
87
"repository": {

packages/host-chat/src/accountService.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type AccountService = {
2323
getConsumerInfo(address: string): ResultAsync<Identity | null, Error>;
2424
};
2525

26-
type Network = 'paseo-next' | 'preview' | 'stable';
26+
type Network = 'paseo-next' | 'paseo-next-v2' | 'preview' | 'stable';
2727

2828
type SearchResponse = {
2929
candidateAccountId: string;
@@ -146,4 +146,10 @@ const NETWORK_CONFIGS: Record<Network, NetworkConfig> = {
146146
wsUrl: 'wss://paseo-people-next-rpc.polkadot.io',
147147
apiUrl: 'https://identity-backend.parity-testnet.parity.io/api/v1',
148148
},
149+
'paseo-next-v2': {
150+
id: 'paseo-next-v2',
151+
name: 'Paseo Next V2',
152+
wsUrl: 'wss://paseo-people-next-system-rpc.polkadot.io',
153+
apiUrl: 'https://identity-backend-next.parity-testnet.parity.io/api/v1',
154+
},
149155
};

0 commit comments

Comments
 (0)