@snapie/chat-client 0.1.0 → 0.2.0
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 +38 -0
- package/dist/{client-Bztyx_3e.d.mts → client-DDdPIACK.d.mts} +17 -0
- package/dist/{client-Bztyx_3e.d.ts → client-DDdPIACK.d.ts} +17 -0
- package/dist/index.d.mts +15 -3
- package/dist/index.d.ts +15 -3
- package/dist/index.js +59 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +58 -2
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +1 -1
- package/dist/react.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -220,6 +220,44 @@ const prefs = await client.getPreferences();
|
|
|
220
220
|
|
|
221
221
|
---
|
|
222
222
|
|
|
223
|
+
## Image uploads & rendering
|
|
224
|
+
|
|
225
|
+
### Uploading an image
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
// Upload a File and get back the public URL
|
|
229
|
+
const url = await client.uploadImage(file, username, async (challenge) => {
|
|
230
|
+
return await keychain.signBuffer(username, challenge, 'Posting');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Embed the URL in a message
|
|
234
|
+
await client.sendMessage(conversationId, 'dm', url);
|
|
235
|
+
// or append to text:
|
|
236
|
+
await client.sendMessage(conversationId, 'dm', `Check this out\n${url}`);
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Rendering image messages
|
|
240
|
+
|
|
241
|
+
Snapie embeds image URLs as plain text inside `message.content`. Use `extractImageUrls` to detect and render them:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
import { extractImageUrls } from '@snapie/chat-client';
|
|
245
|
+
|
|
246
|
+
function MessageBubble({ message }) {
|
|
247
|
+
const images = extractImageUrls(message.content);
|
|
248
|
+
return (
|
|
249
|
+
<div>
|
|
250
|
+
<p>{message.content}</p>
|
|
251
|
+
{images.map(url => <img key={url} src={url} alt="" />)}
|
|
252
|
+
</div>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
`isImageUrl(url)` is also exported if you need to check a single URL.
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
223
261
|
## Push notifications (FCM)
|
|
224
262
|
|
|
225
263
|
```typescript
|
|
@@ -71,6 +71,7 @@ declare class ChatService {
|
|
|
71
71
|
private token;
|
|
72
72
|
private tokenUsername;
|
|
73
73
|
private base;
|
|
74
|
+
private rootUrl;
|
|
74
75
|
private storage;
|
|
75
76
|
constructor(baseUrl: string, storage: StorageAdapter);
|
|
76
77
|
isAuthenticated(): boolean;
|
|
@@ -118,6 +119,13 @@ declare class ChatService {
|
|
|
118
119
|
unblockUser(username: string): Promise<void>;
|
|
119
120
|
registerDevice(fcmToken: string): Promise<void>;
|
|
120
121
|
markDmMemoFallbackSent(conversationId: string): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* Upload an image to the Snapie image server.
|
|
124
|
+
* `signMessage` should sign the filename string with the user's posting key
|
|
125
|
+
* (same signing function used for authentication).
|
|
126
|
+
* Returns the public URL of the uploaded image.
|
|
127
|
+
*/
|
|
128
|
+
uploadImage(file: File, username: string, signMessage: (message: string) => Promise<string>): Promise<string>;
|
|
121
129
|
private buildQS;
|
|
122
130
|
private get;
|
|
123
131
|
private post;
|
|
@@ -214,6 +222,15 @@ declare class ChatClient {
|
|
|
214
222
|
unblockUser(username: string): Promise<void>;
|
|
215
223
|
registerDevice(fcmToken: string): Promise<void>;
|
|
216
224
|
markDmMemoFallbackSent(conversationId: string): Promise<void>;
|
|
225
|
+
/**
|
|
226
|
+
* Upload an image and get back a public URL to embed in a message.
|
|
227
|
+
* After uploading, pass the URL as the message content (or append it to text).
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* const url = await client.uploadImage(file, username, challenge => keychain.sign(challenge));
|
|
231
|
+
* await client.sendMessage(convId, 'dm', url);
|
|
232
|
+
*/
|
|
233
|
+
uploadImage(file: File, username: string, signMessage: (message: string) => Promise<string>): Promise<string>;
|
|
217
234
|
destroy(): void;
|
|
218
235
|
}
|
|
219
236
|
|
|
@@ -71,6 +71,7 @@ declare class ChatService {
|
|
|
71
71
|
private token;
|
|
72
72
|
private tokenUsername;
|
|
73
73
|
private base;
|
|
74
|
+
private rootUrl;
|
|
74
75
|
private storage;
|
|
75
76
|
constructor(baseUrl: string, storage: StorageAdapter);
|
|
76
77
|
isAuthenticated(): boolean;
|
|
@@ -118,6 +119,13 @@ declare class ChatService {
|
|
|
118
119
|
unblockUser(username: string): Promise<void>;
|
|
119
120
|
registerDevice(fcmToken: string): Promise<void>;
|
|
120
121
|
markDmMemoFallbackSent(conversationId: string): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* Upload an image to the Snapie image server.
|
|
124
|
+
* `signMessage` should sign the filename string with the user's posting key
|
|
125
|
+
* (same signing function used for authentication).
|
|
126
|
+
* Returns the public URL of the uploaded image.
|
|
127
|
+
*/
|
|
128
|
+
uploadImage(file: File, username: string, signMessage: (message: string) => Promise<string>): Promise<string>;
|
|
121
129
|
private buildQS;
|
|
122
130
|
private get;
|
|
123
131
|
private post;
|
|
@@ -214,6 +222,15 @@ declare class ChatClient {
|
|
|
214
222
|
unblockUser(username: string): Promise<void>;
|
|
215
223
|
registerDevice(fcmToken: string): Promise<void>;
|
|
216
224
|
markDmMemoFallbackSent(conversationId: string): Promise<void>;
|
|
225
|
+
/**
|
|
226
|
+
* Upload an image and get back a public URL to embed in a message.
|
|
227
|
+
* After uploading, pass the URL as the message content (or append it to text).
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* const url = await client.uploadImage(file, username, challenge => keychain.sign(challenge));
|
|
231
|
+
* await client.sendMessage(convId, 'dm', url);
|
|
232
|
+
*/
|
|
233
|
+
uploadImage(file: File, username: string, signMessage: (message: string) => Promise<string>): Promise<string>;
|
|
217
234
|
destroy(): void;
|
|
218
235
|
}
|
|
219
236
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as StorageAdapter } from './client-
|
|
2
|
-
export { c as Channel, C as ChatClient, b as ChatClientOptions, g as ChatPreferences, a as ChatService, d as Conversation, D as DmDeliveryInfo, f as DmStatusInfo, M as Message, e as MessagesResult, T as TypingStatusInfo } from './client-
|
|
1
|
+
import { S as StorageAdapter } from './client-DDdPIACK.mjs';
|
|
2
|
+
export { c as Channel, C as ChatClient, b as ChatClientOptions, g as ChatPreferences, a as ChatService, d as Conversation, D as DmDeliveryInfo, f as DmStatusInfo, M as Message, e as MessagesResult, T as TypingStatusInfo } from './client-DDdPIACK.mjs';
|
|
3
3
|
|
|
4
4
|
type Handler = () => void | Promise<void>;
|
|
5
5
|
/**
|
|
@@ -19,4 +19,16 @@ declare class PollingManager {
|
|
|
19
19
|
/** Default adapter — uses localStorage when available, falls back to in-memory. */
|
|
20
20
|
declare function createDefaultStorage(): StorageAdapter;
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
declare function isImageUrl(url: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Extract image URLs embedded in a message's content string.
|
|
25
|
+
* Snapie sends images by embedding the URL as plain text in `message.content`.
|
|
26
|
+
* Use this to detect and render inline images.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* const images = extractImageUrls(message.content);
|
|
30
|
+
* images.forEach(url => console.log(<img src={url} />));
|
|
31
|
+
*/
|
|
32
|
+
declare function extractImageUrls(content: string): string[];
|
|
33
|
+
|
|
34
|
+
export { PollingManager, StorageAdapter, createDefaultStorage, extractImageUrls, isImageUrl };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as StorageAdapter } from './client-
|
|
2
|
-
export { c as Channel, C as ChatClient, b as ChatClientOptions, g as ChatPreferences, a as ChatService, d as Conversation, D as DmDeliveryInfo, f as DmStatusInfo, M as Message, e as MessagesResult, T as TypingStatusInfo } from './client-
|
|
1
|
+
import { S as StorageAdapter } from './client-DDdPIACK.js';
|
|
2
|
+
export { c as Channel, C as ChatClient, b as ChatClientOptions, g as ChatPreferences, a as ChatService, d as Conversation, D as DmDeliveryInfo, f as DmStatusInfo, M as Message, e as MessagesResult, T as TypingStatusInfo } from './client-DDdPIACK.js';
|
|
3
3
|
|
|
4
4
|
type Handler = () => void | Promise<void>;
|
|
5
5
|
/**
|
|
@@ -19,4 +19,16 @@ declare class PollingManager {
|
|
|
19
19
|
/** Default adapter — uses localStorage when available, falls back to in-memory. */
|
|
20
20
|
declare function createDefaultStorage(): StorageAdapter;
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
declare function isImageUrl(url: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Extract image URLs embedded in a message's content string.
|
|
25
|
+
* Snapie sends images by embedding the URL as plain text in `message.content`.
|
|
26
|
+
* Use this to detect and render inline images.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* const images = extractImageUrls(message.content);
|
|
30
|
+
* images.forEach(url => console.log(<img src={url} />));
|
|
31
|
+
*/
|
|
32
|
+
declare function extractImageUrls(content: string): string[];
|
|
33
|
+
|
|
34
|
+
export { PollingManager, StorageAdapter, createDefaultStorage, extractImageUrls, isImageUrl };
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,8 @@ var ChatService = class {
|
|
|
7
7
|
constructor(baseUrl, storage) {
|
|
8
8
|
this.token = null;
|
|
9
9
|
this.tokenUsername = null;
|
|
10
|
-
this.
|
|
10
|
+
this.rootUrl = baseUrl.replace(/\/$/, "");
|
|
11
|
+
this.base = `${this.rootUrl}/api/chat`;
|
|
11
12
|
this.storage = storage;
|
|
12
13
|
this.token = storage.getItem(TOKEN_KEY);
|
|
13
14
|
this.tokenUsername = storage.getItem(TOKEN_USER_KEY);
|
|
@@ -171,6 +172,30 @@ var ChatService = class {
|
|
|
171
172
|
async markDmMemoFallbackSent(conversationId) {
|
|
172
173
|
await this.post(`${this.base}/dm/${conversationId}/memo-fallback`, { success: true }, true);
|
|
173
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Upload an image to the Snapie image server.
|
|
177
|
+
* `signMessage` should sign the filename string with the user's posting key
|
|
178
|
+
* (same signing function used for authentication).
|
|
179
|
+
* Returns the public URL of the uploaded image.
|
|
180
|
+
*/
|
|
181
|
+
async uploadImage(file, username, signMessage) {
|
|
182
|
+
const signature = await signMessage(file.name);
|
|
183
|
+
const form = new FormData();
|
|
184
|
+
form.append("file", file);
|
|
185
|
+
form.append("username", username);
|
|
186
|
+
form.append("signature", signature);
|
|
187
|
+
const res = await fetch(`${this.rootUrl}/api/upload-image`, {
|
|
188
|
+
method: "POST",
|
|
189
|
+
body: form
|
|
190
|
+
});
|
|
191
|
+
if (!res.ok) {
|
|
192
|
+
const err = await res.json().catch(() => ({}));
|
|
193
|
+
throw new Error(err.error ?? `HTTP ${res.status}`);
|
|
194
|
+
}
|
|
195
|
+
const data = await res.json();
|
|
196
|
+
if (!data.url) throw new Error("Upload failed: no URL returned");
|
|
197
|
+
return data.url;
|
|
198
|
+
}
|
|
174
199
|
buildQS(opts) {
|
|
175
200
|
const p = new URLSearchParams();
|
|
176
201
|
if (opts.before) p.set("before", opts.before);
|
|
@@ -457,6 +482,18 @@ var ChatClient = class {
|
|
|
457
482
|
markDmMemoFallbackSent(conversationId) {
|
|
458
483
|
return this.service.markDmMemoFallbackSent(conversationId);
|
|
459
484
|
}
|
|
485
|
+
// ── Image uploads ─────────────────────────────────────────────────────────
|
|
486
|
+
/**
|
|
487
|
+
* Upload an image and get back a public URL to embed in a message.
|
|
488
|
+
* After uploading, pass the URL as the message content (or append it to text).
|
|
489
|
+
*
|
|
490
|
+
* @example
|
|
491
|
+
* const url = await client.uploadImage(file, username, challenge => keychain.sign(challenge));
|
|
492
|
+
* await client.sendMessage(convId, 'dm', url);
|
|
493
|
+
*/
|
|
494
|
+
uploadImage(file, username, signMessage) {
|
|
495
|
+
return this.service.uploadImage(file, username, signMessage);
|
|
496
|
+
}
|
|
460
497
|
// ── Cleanup ───────────────────────────────────────────────────────────────
|
|
461
498
|
destroy() {
|
|
462
499
|
this.poller.destroy();
|
|
@@ -464,9 +501,30 @@ var ChatClient = class {
|
|
|
464
501
|
}
|
|
465
502
|
};
|
|
466
503
|
|
|
504
|
+
// src/utils.ts
|
|
505
|
+
var IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif"];
|
|
506
|
+
function isImageUrl(url) {
|
|
507
|
+
const trimmed = url.trim();
|
|
508
|
+
try {
|
|
509
|
+
const parsed = new URL(trimmed);
|
|
510
|
+
if (!["http:", "https:"].includes(parsed.protocol)) return false;
|
|
511
|
+
const pathname = parsed.pathname.toLowerCase();
|
|
512
|
+
return IMAGE_EXTENSIONS.some((ext) => pathname.endsWith(ext));
|
|
513
|
+
} catch {
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function extractImageUrls(content) {
|
|
518
|
+
if (!content) return [];
|
|
519
|
+
const urls = content.match(/https?:\/\/[^\s)]+/gi) ?? [];
|
|
520
|
+
return urls.filter(isImageUrl);
|
|
521
|
+
}
|
|
522
|
+
|
|
467
523
|
exports.ChatClient = ChatClient;
|
|
468
524
|
exports.ChatService = ChatService;
|
|
469
525
|
exports.PollingManager = PollingManager;
|
|
470
526
|
exports.createDefaultStorage = createDefaultStorage;
|
|
527
|
+
exports.extractImageUrls = extractImageUrls;
|
|
528
|
+
exports.isImageUrl = isImageUrl;
|
|
471
529
|
//# sourceMappingURL=index.js.map
|
|
472
530
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/service.ts","../src/polling.ts","../src/storage.ts","../src/client.ts"],"names":["fetch"],"mappings":"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","file":"index.js","sourcesContent":["import type {\n Channel,\n Conversation,\n Message,\n MessagesResult,\n DmDeliveryInfo,\n TypingStatusInfo,\n ChatPreferences,\n StorageAdapter,\n} from './types';\n\nconst TOKEN_KEY = 'snapie-chat-token';\nconst TOKEN_USER_KEY = 'snapie-chat-token-user';\n\nexport class ChatService {\n private token: string | null = null;\n private tokenUsername: string | null = null;\n private base: string;\n private storage: StorageAdapter;\n\n constructor(baseUrl: string, storage: StorageAdapter) {\n this.base = `${baseUrl.replace(/\\/$/, '')}/api/chat`;\n this.storage = storage;\n this.token = storage.getItem(TOKEN_KEY);\n this.tokenUsername = storage.getItem(TOKEN_USER_KEY);\n }\n\n isAuthenticated(): boolean {\n return !!this.token;\n }\n\n getTokenUsername(): string | null {\n return this.tokenUsername;\n }\n\n async authenticate(\n username: string,\n signMessage: (msg: string) => Promise<string>\n ): Promise<void> {\n const { challenge } = await this.post<{ challenge: string }>(\n `${this.base}/auth/challenge`,\n { username },\n false\n );\n const signature = await signMessage(challenge);\n const { token } = await this.post<{ token: string }>(\n `${this.base}/auth/verify`,\n { username, challenge, signature },\n false\n );\n this.token = token;\n this.tokenUsername = username;\n this.storage.setItem(TOKEN_KEY, token);\n this.storage.setItem(TOKEN_USER_KEY, username);\n }\n\n logout(): void {\n this.token = null;\n this.tokenUsername = null;\n this.storage.removeItem(TOKEN_KEY);\n this.storage.removeItem(TOKEN_USER_KEY);\n }\n\n async getChannels(): Promise<Channel[]> {\n const { channels } = await this.get<{ channels: Channel[] }>(`${this.base}/channels`, false);\n return channels;\n }\n\n async getConversations(): Promise<Conversation[]> {\n const { conversations } = await this.get<{ conversations: Conversation[] }>(`${this.base}/conversations`, true);\n return conversations;\n }\n\n async getChannelMessages(\n channelId: string,\n opts: { before?: string; after?: string; limit?: number } = {}\n ): Promise<Message[]> {\n const qs = this.buildQS(opts);\n const { messages } = await this.get<{ messages: Message[] }>(\n `${this.base}/channels/${channelId}/messages${qs}`,\n true\n );\n return messages;\n }\n\n async sendChannelMessage(channelId: string, content: string, replyTo?: string): Promise<Message> {\n const { message } = await this.post<{ message: Message }>(\n `${this.base}/channels/${channelId}/messages`,\n { content, replyTo: replyTo ?? null },\n true\n );\n return message;\n }\n\n async editChannelMessage(channelId: string, messageId: string, content: string): Promise<Message> {\n const { message } = await this.request<{ message: Message }>(\n `${this.base}/channels/${channelId}/messages`,\n { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messageId, content }) },\n true\n );\n return message;\n }\n\n async joinChannel(channelId: string): Promise<void> {\n await this.post(`${this.base}/channels/${channelId}/join`, {}, true);\n }\n\n async leaveChannel(channelId: string): Promise<void> {\n await this.post(`${this.base}/channels/${channelId}/leave`, {}, true);\n }\n\n async getDmMessages(\n conversationId: string,\n opts: { before?: string; after?: string; limit?: number } = {}\n ): Promise<MessagesResult> {\n const qs = this.buildQS(opts);\n const { messages, status } = await this.get<MessagesResult>(\n `${this.base}/dm/${conversationId}/messages${qs}`,\n true\n );\n return { messages, status };\n }\n\n async sendDmMessage(\n conversationId: string,\n content: string,\n replyTo?: string\n ): Promise<{ message: Message; delivery?: DmDeliveryInfo }> {\n return this.post<{ message: Message; delivery?: DmDeliveryInfo }>(\n `${this.base}/dm/${conversationId}/messages`,\n { content, replyTo: replyTo ?? null },\n true\n );\n }\n\n async editDmMessage(conversationId: string, messageId: string, content: string): Promise<Message> {\n const { message } = await this.request<{ message: Message }>(\n `${this.base}/dm/${conversationId}/messages`,\n { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messageId, content }) },\n true\n );\n return message;\n }\n\n async openDm(targetUser: string): Promise<Conversation> {\n const { conversation } = await this.post<{ conversation: Conversation }>(\n `${this.base}/dm`,\n { targetUser },\n true\n );\n return conversation;\n }\n\n async createGroup(payload: {\n name: string;\n description?: string;\n isPublic?: boolean;\n members?: string[];\n }): Promise<Channel> {\n const { group } = await this.post<{ group: Channel }>(`${this.base}/groups`, payload, true);\n return group;\n }\n\n async getGroups(): Promise<Channel[]> {\n const { groups } = await this.get<{ groups: Channel[] }>(`${this.base}/groups`, true);\n return groups;\n }\n\n async addGroupMember(groupId: string, member: string): Promise<Channel> {\n const { group } = await this.post<{ group: Channel }>(\n `${this.base}/groups/${groupId}/members`,\n { member },\n true\n );\n return group;\n }\n\n async removeGroupMember(groupId: string, member: string): Promise<Channel> {\n const { group } = await this.request<{ group: Channel }>(\n `${this.base}/groups/${groupId}/members`,\n { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ member }) },\n true\n );\n return group;\n }\n\n async getUnreadCount(): Promise<number> {\n if (!this.token) return 0;\n try {\n const { unread } = await this.get<{ unread: number }>(`${this.base}/unread`, true);\n return unread;\n } catch {\n return 0;\n }\n }\n\n async setTyping(conversationId: string, isTyping: boolean): Promise<void> {\n await this.post(`${this.base}/typing`, { conversationId, isTyping }, true);\n }\n\n async getTyping(conversationId: string): Promise<TypingStatusInfo> {\n const qs = new URLSearchParams({ conversationId }).toString();\n return this.get<TypingStatusInfo>(`${this.base}/typing?${qs}`, true);\n }\n\n async getPreferences(): Promise<ChatPreferences> {\n return this.get<ChatPreferences>(`${this.base}/preferences`, true);\n }\n\n async muteUser(username: string): Promise<void> {\n await this.post(`${this.base}/preferences`, { action: 'mute', target: username }, true);\n }\n\n async unmuteUser(username: string): Promise<void> {\n await this.post(`${this.base}/preferences`, { action: 'unmute', target: username }, true);\n }\n\n async blockUser(username: string): Promise<void> {\n await this.post(`${this.base}/preferences`, { action: 'block', target: username }, true);\n }\n\n async unblockUser(username: string): Promise<void> {\n await this.post(`${this.base}/preferences`, { action: 'unblock', target: username }, true);\n }\n\n async registerDevice(fcmToken: string): Promise<void> {\n await this.post(`${this.base}/register-device`, { fcmToken }, true);\n }\n\n async markDmMemoFallbackSent(conversationId: string): Promise<void> {\n await this.post(`${this.base}/dm/${conversationId}/memo-fallback`, { success: true }, true);\n }\n\n private buildQS(opts: { before?: string; after?: string; limit?: number }): string {\n const p = new URLSearchParams();\n if (opts.before) p.set('before', opts.before);\n if (opts.after) p.set('after', opts.after);\n if (opts.limit) p.set('limit', String(opts.limit));\n const s = p.toString();\n return s ? `?${s}` : '';\n }\n\n private async get<T>(url: string, auth: boolean): Promise<T> {\n return this.request<T>(url, { method: 'GET' }, auth);\n }\n\n private async post<T>(url: string, body: unknown, auth: boolean): Promise<T> {\n return this.request<T>(\n url,\n { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) },\n auth\n );\n }\n\n async request<T>(url: string, opts: RequestInit, auth: boolean): Promise<T> {\n const headers: Record<string, string> = { ...(opts.headers as Record<string, string>) };\n if (auth && this.token) headers['Authorization'] = `Bearer ${this.token}`;\n\n const res = await fetch(url, { ...opts, headers });\n\n if (res.status === 401 && auth) {\n this.token = null;\n this.storage.removeItem(TOKEN_KEY);\n throw new Error('CHAT_UNAUTHORIZED');\n }\n\n if (!res.ok) {\n const err = await res.json().catch(() => ({}));\n throw new Error((err as { error?: string }).error ?? `HTTP ${res.status}`);\n }\n\n return res.json() as Promise<T>;\n }\n}\n","type Handler = () => void | Promise<void>;\n\n/**\n * Manages a single setInterval that fans out to multiple subscribers.\n * Starting the first subscription starts the timer; removing the last stops it.\n */\nexport class PollingManager {\n private timer: ReturnType<typeof setInterval> | null = null;\n private handlers: Set<Handler> = new Set();\n private interval: number;\n\n constructor(intervalMs: number) {\n this.interval = intervalMs;\n }\n\n subscribe(handler: Handler): () => void {\n this.handlers.add(handler);\n if (!this.timer) {\n this.timer = setInterval(() => this.tick(), this.interval);\n }\n return () => {\n this.handlers.delete(handler);\n if (this.handlers.size === 0 && this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n };\n }\n\n private tick() {\n for (const h of this.handlers) {\n try { h(); } catch { /* individual handler errors don't break others */ }\n }\n }\n\n destroy() {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n this.handlers.clear();\n }\n}\n","import type { StorageAdapter } from './types';\n\n/** Default adapter — uses localStorage when available, falls back to in-memory. */\nexport function createDefaultStorage(): StorageAdapter {\n if (typeof window !== 'undefined' && window.localStorage) {\n return window.localStorage;\n }\n const mem: Record<string, string> = {};\n return {\n getItem: (k) => mem[k] ?? null,\n setItem: (k, v) => { mem[k] = v; },\n removeItem: (k) => { delete mem[k]; },\n };\n}\n","import { ChatService } from './service';\nimport { PollingManager } from './polling';\nimport { createDefaultStorage } from './storage';\nimport type {\n ChatClientOptions,\n Channel,\n Conversation,\n Message,\n MessagesResult,\n ChatPreferences,\n TypingStatusInfo,\n DmDeliveryInfo,\n} from './types';\n\ntype ConversationsCallback = (conversations: Conversation[]) => void;\ntype MessagesCallback = (messages: Message[]) => void;\ntype UnreadCallback = (count: number) => void;\n\n/**\n * High-level Snapie chat client.\n *\n * Usage:\n * const client = new ChatClient({ baseUrl: 'https://snapie.io' });\n * await client.authenticate(username, msg => keychain.sign(msg));\n * const conversations = await client.getConversations();\n * const unsub = client.subscribeToMessages(convId, 'dm', msgs => setMessages(msgs));\n */\nexport class ChatClient {\n readonly service: ChatService;\n private poller: PollingManager;\n\n /** Cache of messages per conversationId — avoids re-rendering unchanged data */\n private messageCache: Map<string, Message[]> = new Map();\n\n constructor(options: ChatClientOptions) {\n const storage = options.storage ?? createDefaultStorage();\n this.service = new ChatService(options.baseUrl, storage);\n this.poller = new PollingManager(options.pollInterval ?? 15_000);\n }\n\n // ── Auth ─────────────────────────────────────────────────────────────────\n\n isAuthenticated(): boolean {\n return this.service.isAuthenticated();\n }\n\n getUsername(): string | null {\n return this.service.getTokenUsername();\n }\n\n /**\n * Authenticate with a Hive account.\n * `signMessage` should call Hive Keychain or equivalent with the posting key.\n */\n async authenticate(\n username: string,\n signMessage: (challenge: string) => Promise<string>\n ): Promise<void> {\n return this.service.authenticate(username, signMessage);\n }\n\n logout(): void {\n this.service.logout();\n this.messageCache.clear();\n }\n\n // ── Conversations ────────────────────────────────────────────────────────\n\n getConversations(): Promise<Conversation[]> {\n return this.service.getConversations();\n }\n\n openDm(targetUser: string): Promise<Conversation> {\n return this.service.openDm(targetUser);\n }\n\n // ── Channels ─────────────────────────────────────────────────────────────\n\n getChannels(): Promise<Channel[]> {\n return this.service.getChannels();\n }\n\n getGroups(): Promise<Channel[]> {\n return this.service.getGroups();\n }\n\n joinChannel(channelId: string): Promise<void> {\n return this.service.joinChannel(channelId);\n }\n\n leaveChannel(channelId: string): Promise<void> {\n return this.service.leaveChannel(channelId);\n }\n\n createGroup(payload: {\n name: string;\n description?: string;\n isPublic?: boolean;\n members?: string[];\n }): Promise<Channel> {\n return this.service.createGroup(payload);\n }\n\n addGroupMember(groupId: string, member: string): Promise<Channel> {\n return this.service.addGroupMember(groupId, member);\n }\n\n removeGroupMember(groupId: string, member: string): Promise<Channel> {\n return this.service.removeGroupMember(groupId, member);\n }\n\n // ── Messages ─────────────────────────────────────────────────────────────\n\n async getMessages(\n conversationId: string,\n type: 'channel' | 'dm' | 'group',\n opts: { before?: string; after?: string; limit?: number } = {}\n ): Promise<MessagesResult> {\n if (type === 'dm') {\n return this.service.getDmMessages(conversationId, opts);\n }\n const messages = await this.service.getChannelMessages(conversationId, opts);\n return { messages };\n }\n\n async sendMessage(\n conversationId: string,\n type: 'channel' | 'dm' | 'group',\n content: string,\n replyTo?: string\n ): Promise<{ message: Message; delivery?: DmDeliveryInfo }> {\n if (type === 'dm') {\n return this.service.sendDmMessage(conversationId, content, replyTo);\n }\n const message = await this.service.sendChannelMessage(conversationId, content, replyTo);\n return { message };\n }\n\n async editMessage(\n conversationId: string,\n type: 'channel' | 'dm' | 'group',\n messageId: string,\n content: string\n ): Promise<Message> {\n if (type === 'dm') {\n return this.service.editDmMessage(conversationId, messageId, content);\n }\n return this.service.editChannelMessage(conversationId, messageId, content);\n }\n\n // ── Real-time subscriptions ───────────────────────────────────────────────\n\n /**\n * Subscribe to live updates for a conversation's message list.\n * Calls `callback` immediately with the current messages, then again on every poll tick.\n * Returns an unsubscribe function.\n *\n * @example\n * const unsub = client.subscribeToMessages(conv._id, conv.type, msgs => setMessages(msgs));\n * // later:\n * unsub();\n */\n subscribeToMessages(\n conversationId: string,\n type: 'channel' | 'dm' | 'group',\n callback: MessagesCallback\n ): () => void {\n let latestMessageId: string | undefined;\n\n const fetch = async () => {\n try {\n const opts = latestMessageId ? { after: latestMessageId } : { limit: 40 };\n const { messages: incoming } = await this.getMessages(conversationId, type, opts);\n if (incoming.length === 0) return;\n\n const existing = this.messageCache.get(conversationId) ?? [];\n\n if (latestMessageId) {\n // Append only genuinely new messages\n const existingIds = new Set(existing.map(m => m._id));\n const fresh = incoming.filter(m => !existingIds.has(m._id));\n if (fresh.length === 0) return;\n const merged = [...existing, ...fresh];\n this.messageCache.set(conversationId, merged);\n callback(merged);\n } else {\n // Initial load\n this.messageCache.set(conversationId, incoming);\n callback(incoming);\n }\n\n latestMessageId = incoming[incoming.length - 1]._id;\n } catch {\n // Silently retry on next tick\n }\n };\n\n // Fire immediately, then on each poll tick\n fetch();\n const stopPolling = this.poller.subscribe(fetch);\n\n return () => {\n stopPolling();\n };\n }\n\n /**\n * Subscribe to the full conversations list, refreshed on every poll tick.\n * Returns an unsubscribe function.\n */\n subscribeToConversations(callback: ConversationsCallback): () => void {\n const fetch = async () => {\n try {\n const conversations = await this.service.getConversations();\n callback(conversations);\n } catch {\n // Silently retry on next tick\n }\n };\n\n fetch();\n return this.poller.subscribe(fetch);\n }\n\n /**\n * Subscribe to the unread message count, refreshed on every poll tick.\n * Returns an unsubscribe function.\n */\n subscribeToUnreadCount(callback: UnreadCallback): () => void {\n const fetch = async () => {\n try {\n const count = await this.service.getUnreadCount();\n callback(count);\n } catch {\n callback(0);\n }\n };\n\n fetch();\n return this.poller.subscribe(fetch);\n }\n\n /**\n * Notify the client of an incoming FCM foreground message.\n * Call this from your FCM `onMessage` handler to trigger an immediate refresh\n * rather than waiting for the next poll tick.\n *\n * @example\n * onMessage(messaging, () => client.onForegroundPush());\n */\n onForegroundPush(): void {\n for (const handler of (this.poller as unknown as { handlers: Set<() => void> }).handlers) {\n try { handler(); } catch { /* */ }\n }\n }\n\n // ── Presence & typing ────────────────────────────────────────────────────\n\n setTyping(conversationId: string, isTyping: boolean): Promise<void> {\n return this.service.setTyping(conversationId, isTyping);\n }\n\n getTyping(conversationId: string): Promise<TypingStatusInfo> {\n return this.service.getTyping(conversationId);\n }\n\n getUnreadCount(): Promise<number> {\n return this.service.getUnreadCount();\n }\n\n // ── Preferences ──────────────────────────────────────────────────────────\n\n getPreferences(): Promise<ChatPreferences> {\n return this.service.getPreferences();\n }\n\n muteUser(username: string): Promise<void> {\n return this.service.muteUser(username);\n }\n\n unmuteUser(username: string): Promise<void> {\n return this.service.unmuteUser(username);\n }\n\n blockUser(username: string): Promise<void> {\n return this.service.blockUser(username);\n }\n\n unblockUser(username: string): Promise<void> {\n return this.service.unblockUser(username);\n }\n\n // ── Push notifications ────────────────────────────────────────────────────\n\n registerDevice(fcmToken: string): Promise<void> {\n return this.service.registerDevice(fcmToken);\n }\n\n markDmMemoFallbackSent(conversationId: string): Promise<void> {\n return this.service.markDmMemoFallbackSent(conversationId);\n }\n\n // ── Cleanup ───────────────────────────────────────────────────────────────\n\n destroy(): void {\n this.poller.destroy();\n this.messageCache.clear();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/service.ts","../src/polling.ts","../src/storage.ts","../src/client.ts","../src/utils.ts"],"names":["fetch"],"mappings":"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","file":"index.js","sourcesContent":["import type {\n Channel,\n Conversation,\n Message,\n MessagesResult,\n DmDeliveryInfo,\n TypingStatusInfo,\n ChatPreferences,\n StorageAdapter,\n} from './types';\n\nconst TOKEN_KEY = 'snapie-chat-token';\nconst TOKEN_USER_KEY = 'snapie-chat-token-user';\n\nexport class ChatService {\n private token: string | null = null;\n private tokenUsername: string | null = null;\n private base: string;\n private rootUrl: string;\n private storage: StorageAdapter;\n\n constructor(baseUrl: string, storage: StorageAdapter) {\n this.rootUrl = baseUrl.replace(/\\/$/, '');\n this.base = `${this.rootUrl}/api/chat`;\n this.storage = storage;\n this.token = storage.getItem(TOKEN_KEY);\n this.tokenUsername = storage.getItem(TOKEN_USER_KEY);\n }\n\n isAuthenticated(): boolean {\n return !!this.token;\n }\n\n getTokenUsername(): string | null {\n return this.tokenUsername;\n }\n\n async authenticate(\n username: string,\n signMessage: (msg: string) => Promise<string>\n ): Promise<void> {\n const { challenge } = await this.post<{ challenge: string }>(\n `${this.base}/auth/challenge`,\n { username },\n false\n );\n const signature = await signMessage(challenge);\n const { token } = await this.post<{ token: string }>(\n `${this.base}/auth/verify`,\n { username, challenge, signature },\n false\n );\n this.token = token;\n this.tokenUsername = username;\n this.storage.setItem(TOKEN_KEY, token);\n this.storage.setItem(TOKEN_USER_KEY, username);\n }\n\n logout(): void {\n this.token = null;\n this.tokenUsername = null;\n this.storage.removeItem(TOKEN_KEY);\n this.storage.removeItem(TOKEN_USER_KEY);\n }\n\n async getChannels(): Promise<Channel[]> {\n const { channels } = await this.get<{ channels: Channel[] }>(`${this.base}/channels`, false);\n return channels;\n }\n\n async getConversations(): Promise<Conversation[]> {\n const { conversations } = await this.get<{ conversations: Conversation[] }>(`${this.base}/conversations`, true);\n return conversations;\n }\n\n async getChannelMessages(\n channelId: string,\n opts: { before?: string; after?: string; limit?: number } = {}\n ): Promise<Message[]> {\n const qs = this.buildQS(opts);\n const { messages } = await this.get<{ messages: Message[] }>(\n `${this.base}/channels/${channelId}/messages${qs}`,\n true\n );\n return messages;\n }\n\n async sendChannelMessage(channelId: string, content: string, replyTo?: string): Promise<Message> {\n const { message } = await this.post<{ message: Message }>(\n `${this.base}/channels/${channelId}/messages`,\n { content, replyTo: replyTo ?? null },\n true\n );\n return message;\n }\n\n async editChannelMessage(channelId: string, messageId: string, content: string): Promise<Message> {\n const { message } = await this.request<{ message: Message }>(\n `${this.base}/channels/${channelId}/messages`,\n { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messageId, content }) },\n true\n );\n return message;\n }\n\n async joinChannel(channelId: string): Promise<void> {\n await this.post(`${this.base}/channels/${channelId}/join`, {}, true);\n }\n\n async leaveChannel(channelId: string): Promise<void> {\n await this.post(`${this.base}/channels/${channelId}/leave`, {}, true);\n }\n\n async getDmMessages(\n conversationId: string,\n opts: { before?: string; after?: string; limit?: number } = {}\n ): Promise<MessagesResult> {\n const qs = this.buildQS(opts);\n const { messages, status } = await this.get<MessagesResult>(\n `${this.base}/dm/${conversationId}/messages${qs}`,\n true\n );\n return { messages, status };\n }\n\n async sendDmMessage(\n conversationId: string,\n content: string,\n replyTo?: string\n ): Promise<{ message: Message; delivery?: DmDeliveryInfo }> {\n return this.post<{ message: Message; delivery?: DmDeliveryInfo }>(\n `${this.base}/dm/${conversationId}/messages`,\n { content, replyTo: replyTo ?? null },\n true\n );\n }\n\n async editDmMessage(conversationId: string, messageId: string, content: string): Promise<Message> {\n const { message } = await this.request<{ message: Message }>(\n `${this.base}/dm/${conversationId}/messages`,\n { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messageId, content }) },\n true\n );\n return message;\n }\n\n async openDm(targetUser: string): Promise<Conversation> {\n const { conversation } = await this.post<{ conversation: Conversation }>(\n `${this.base}/dm`,\n { targetUser },\n true\n );\n return conversation;\n }\n\n async createGroup(payload: {\n name: string;\n description?: string;\n isPublic?: boolean;\n members?: string[];\n }): Promise<Channel> {\n const { group } = await this.post<{ group: Channel }>(`${this.base}/groups`, payload, true);\n return group;\n }\n\n async getGroups(): Promise<Channel[]> {\n const { groups } = await this.get<{ groups: Channel[] }>(`${this.base}/groups`, true);\n return groups;\n }\n\n async addGroupMember(groupId: string, member: string): Promise<Channel> {\n const { group } = await this.post<{ group: Channel }>(\n `${this.base}/groups/${groupId}/members`,\n { member },\n true\n );\n return group;\n }\n\n async removeGroupMember(groupId: string, member: string): Promise<Channel> {\n const { group } = await this.request<{ group: Channel }>(\n `${this.base}/groups/${groupId}/members`,\n { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ member }) },\n true\n );\n return group;\n }\n\n async getUnreadCount(): Promise<number> {\n if (!this.token) return 0;\n try {\n const { unread } = await this.get<{ unread: number }>(`${this.base}/unread`, true);\n return unread;\n } catch {\n return 0;\n }\n }\n\n async setTyping(conversationId: string, isTyping: boolean): Promise<void> {\n await this.post(`${this.base}/typing`, { conversationId, isTyping }, true);\n }\n\n async getTyping(conversationId: string): Promise<TypingStatusInfo> {\n const qs = new URLSearchParams({ conversationId }).toString();\n return this.get<TypingStatusInfo>(`${this.base}/typing?${qs}`, true);\n }\n\n async getPreferences(): Promise<ChatPreferences> {\n return this.get<ChatPreferences>(`${this.base}/preferences`, true);\n }\n\n async muteUser(username: string): Promise<void> {\n await this.post(`${this.base}/preferences`, { action: 'mute', target: username }, true);\n }\n\n async unmuteUser(username: string): Promise<void> {\n await this.post(`${this.base}/preferences`, { action: 'unmute', target: username }, true);\n }\n\n async blockUser(username: string): Promise<void> {\n await this.post(`${this.base}/preferences`, { action: 'block', target: username }, true);\n }\n\n async unblockUser(username: string): Promise<void> {\n await this.post(`${this.base}/preferences`, { action: 'unblock', target: username }, true);\n }\n\n async registerDevice(fcmToken: string): Promise<void> {\n await this.post(`${this.base}/register-device`, { fcmToken }, true);\n }\n\n async markDmMemoFallbackSent(conversationId: string): Promise<void> {\n await this.post(`${this.base}/dm/${conversationId}/memo-fallback`, { success: true }, true);\n }\n\n /**\n * Upload an image to the Snapie image server.\n * `signMessage` should sign the filename string with the user's posting key\n * (same signing function used for authentication).\n * Returns the public URL of the uploaded image.\n */\n async uploadImage(\n file: File,\n username: string,\n signMessage: (message: string) => Promise<string>\n ): Promise<string> {\n const signature = await signMessage(file.name);\n const form = new FormData();\n form.append('file', file);\n form.append('username', username);\n form.append('signature', signature);\n\n const res = await fetch(`${this.rootUrl}/api/upload-image`, {\n method: 'POST',\n body: form,\n });\n\n if (!res.ok) {\n const err = await res.json().catch(() => ({}));\n throw new Error((err as { error?: string }).error ?? `HTTP ${res.status}`);\n }\n\n const data = await res.json() as { url?: string; error?: string };\n if (!data.url) throw new Error('Upload failed: no URL returned');\n return data.url;\n }\n\n private buildQS(opts: { before?: string; after?: string; limit?: number }): string {\n const p = new URLSearchParams();\n if (opts.before) p.set('before', opts.before);\n if (opts.after) p.set('after', opts.after);\n if (opts.limit) p.set('limit', String(opts.limit));\n const s = p.toString();\n return s ? `?${s}` : '';\n }\n\n private async get<T>(url: string, auth: boolean): Promise<T> {\n return this.request<T>(url, { method: 'GET' }, auth);\n }\n\n private async post<T>(url: string, body: unknown, auth: boolean): Promise<T> {\n return this.request<T>(\n url,\n { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) },\n auth\n );\n }\n\n async request<T>(url: string, opts: RequestInit, auth: boolean): Promise<T> {\n const headers: Record<string, string> = { ...(opts.headers as Record<string, string>) };\n if (auth && this.token) headers['Authorization'] = `Bearer ${this.token}`;\n\n const res = await fetch(url, { ...opts, headers });\n\n if (res.status === 401 && auth) {\n this.token = null;\n this.storage.removeItem(TOKEN_KEY);\n throw new Error('CHAT_UNAUTHORIZED');\n }\n\n if (!res.ok) {\n const err = await res.json().catch(() => ({}));\n throw new Error((err as { error?: string }).error ?? `HTTP ${res.status}`);\n }\n\n return res.json() as Promise<T>;\n }\n}\n","type Handler = () => void | Promise<void>;\n\n/**\n * Manages a single setInterval that fans out to multiple subscribers.\n * Starting the first subscription starts the timer; removing the last stops it.\n */\nexport class PollingManager {\n private timer: ReturnType<typeof setInterval> | null = null;\n private handlers: Set<Handler> = new Set();\n private interval: number;\n\n constructor(intervalMs: number) {\n this.interval = intervalMs;\n }\n\n subscribe(handler: Handler): () => void {\n this.handlers.add(handler);\n if (!this.timer) {\n this.timer = setInterval(() => this.tick(), this.interval);\n }\n return () => {\n this.handlers.delete(handler);\n if (this.handlers.size === 0 && this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n };\n }\n\n private tick() {\n for (const h of this.handlers) {\n try { h(); } catch { /* individual handler errors don't break others */ }\n }\n }\n\n destroy() {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n this.handlers.clear();\n }\n}\n","import type { StorageAdapter } from './types';\n\n/** Default adapter — uses localStorage when available, falls back to in-memory. */\nexport function createDefaultStorage(): StorageAdapter {\n if (typeof window !== 'undefined' && window.localStorage) {\n return window.localStorage;\n }\n const mem: Record<string, string> = {};\n return {\n getItem: (k) => mem[k] ?? null,\n setItem: (k, v) => { mem[k] = v; },\n removeItem: (k) => { delete mem[k]; },\n };\n}\n","import { ChatService } from './service';\nimport { PollingManager } from './polling';\nimport { createDefaultStorage } from './storage';\nimport type {\n ChatClientOptions,\n Channel,\n Conversation,\n Message,\n MessagesResult,\n ChatPreferences,\n TypingStatusInfo,\n DmDeliveryInfo,\n} from './types';\n\ntype ConversationsCallback = (conversations: Conversation[]) => void;\ntype MessagesCallback = (messages: Message[]) => void;\ntype UnreadCallback = (count: number) => void;\n\n/**\n * High-level Snapie chat client.\n *\n * Usage:\n * const client = new ChatClient({ baseUrl: 'https://snapie.io' });\n * await client.authenticate(username, msg => keychain.sign(msg));\n * const conversations = await client.getConversations();\n * const unsub = client.subscribeToMessages(convId, 'dm', msgs => setMessages(msgs));\n */\nexport class ChatClient {\n readonly service: ChatService;\n private poller: PollingManager;\n\n /** Cache of messages per conversationId — avoids re-rendering unchanged data */\n private messageCache: Map<string, Message[]> = new Map();\n\n constructor(options: ChatClientOptions) {\n const storage = options.storage ?? createDefaultStorage();\n this.service = new ChatService(options.baseUrl, storage);\n this.poller = new PollingManager(options.pollInterval ?? 15_000);\n }\n\n // ── Auth ─────────────────────────────────────────────────────────────────\n\n isAuthenticated(): boolean {\n return this.service.isAuthenticated();\n }\n\n getUsername(): string | null {\n return this.service.getTokenUsername();\n }\n\n /**\n * Authenticate with a Hive account.\n * `signMessage` should call Hive Keychain or equivalent with the posting key.\n */\n async authenticate(\n username: string,\n signMessage: (challenge: string) => Promise<string>\n ): Promise<void> {\n return this.service.authenticate(username, signMessage);\n }\n\n logout(): void {\n this.service.logout();\n this.messageCache.clear();\n }\n\n // ── Conversations ────────────────────────────────────────────────────────\n\n getConversations(): Promise<Conversation[]> {\n return this.service.getConversations();\n }\n\n openDm(targetUser: string): Promise<Conversation> {\n return this.service.openDm(targetUser);\n }\n\n // ── Channels ─────────────────────────────────────────────────────────────\n\n getChannels(): Promise<Channel[]> {\n return this.service.getChannels();\n }\n\n getGroups(): Promise<Channel[]> {\n return this.service.getGroups();\n }\n\n joinChannel(channelId: string): Promise<void> {\n return this.service.joinChannel(channelId);\n }\n\n leaveChannel(channelId: string): Promise<void> {\n return this.service.leaveChannel(channelId);\n }\n\n createGroup(payload: {\n name: string;\n description?: string;\n isPublic?: boolean;\n members?: string[];\n }): Promise<Channel> {\n return this.service.createGroup(payload);\n }\n\n addGroupMember(groupId: string, member: string): Promise<Channel> {\n return this.service.addGroupMember(groupId, member);\n }\n\n removeGroupMember(groupId: string, member: string): Promise<Channel> {\n return this.service.removeGroupMember(groupId, member);\n }\n\n // ── Messages ─────────────────────────────────────────────────────────────\n\n async getMessages(\n conversationId: string,\n type: 'channel' | 'dm' | 'group',\n opts: { before?: string; after?: string; limit?: number } = {}\n ): Promise<MessagesResult> {\n if (type === 'dm') {\n return this.service.getDmMessages(conversationId, opts);\n }\n const messages = await this.service.getChannelMessages(conversationId, opts);\n return { messages };\n }\n\n async sendMessage(\n conversationId: string,\n type: 'channel' | 'dm' | 'group',\n content: string,\n replyTo?: string\n ): Promise<{ message: Message; delivery?: DmDeliveryInfo }> {\n if (type === 'dm') {\n return this.service.sendDmMessage(conversationId, content, replyTo);\n }\n const message = await this.service.sendChannelMessage(conversationId, content, replyTo);\n return { message };\n }\n\n async editMessage(\n conversationId: string,\n type: 'channel' | 'dm' | 'group',\n messageId: string,\n content: string\n ): Promise<Message> {\n if (type === 'dm') {\n return this.service.editDmMessage(conversationId, messageId, content);\n }\n return this.service.editChannelMessage(conversationId, messageId, content);\n }\n\n // ── Real-time subscriptions ───────────────────────────────────────────────\n\n /**\n * Subscribe to live updates for a conversation's message list.\n * Calls `callback` immediately with the current messages, then again on every poll tick.\n * Returns an unsubscribe function.\n *\n * @example\n * const unsub = client.subscribeToMessages(conv._id, conv.type, msgs => setMessages(msgs));\n * // later:\n * unsub();\n */\n subscribeToMessages(\n conversationId: string,\n type: 'channel' | 'dm' | 'group',\n callback: MessagesCallback\n ): () => void {\n let latestMessageId: string | undefined;\n\n const fetch = async () => {\n try {\n const opts = latestMessageId ? { after: latestMessageId } : { limit: 40 };\n const { messages: incoming } = await this.getMessages(conversationId, type, opts);\n if (incoming.length === 0) return;\n\n const existing = this.messageCache.get(conversationId) ?? [];\n\n if (latestMessageId) {\n // Append only genuinely new messages\n const existingIds = new Set(existing.map(m => m._id));\n const fresh = incoming.filter(m => !existingIds.has(m._id));\n if (fresh.length === 0) return;\n const merged = [...existing, ...fresh];\n this.messageCache.set(conversationId, merged);\n callback(merged);\n } else {\n // Initial load\n this.messageCache.set(conversationId, incoming);\n callback(incoming);\n }\n\n latestMessageId = incoming[incoming.length - 1]._id;\n } catch {\n // Silently retry on next tick\n }\n };\n\n // Fire immediately, then on each poll tick\n fetch();\n const stopPolling = this.poller.subscribe(fetch);\n\n return () => {\n stopPolling();\n };\n }\n\n /**\n * Subscribe to the full conversations list, refreshed on every poll tick.\n * Returns an unsubscribe function.\n */\n subscribeToConversations(callback: ConversationsCallback): () => void {\n const fetch = async () => {\n try {\n const conversations = await this.service.getConversations();\n callback(conversations);\n } catch {\n // Silently retry on next tick\n }\n };\n\n fetch();\n return this.poller.subscribe(fetch);\n }\n\n /**\n * Subscribe to the unread message count, refreshed on every poll tick.\n * Returns an unsubscribe function.\n */\n subscribeToUnreadCount(callback: UnreadCallback): () => void {\n const fetch = async () => {\n try {\n const count = await this.service.getUnreadCount();\n callback(count);\n } catch {\n callback(0);\n }\n };\n\n fetch();\n return this.poller.subscribe(fetch);\n }\n\n /**\n * Notify the client of an incoming FCM foreground message.\n * Call this from your FCM `onMessage` handler to trigger an immediate refresh\n * rather than waiting for the next poll tick.\n *\n * @example\n * onMessage(messaging, () => client.onForegroundPush());\n */\n onForegroundPush(): void {\n for (const handler of (this.poller as unknown as { handlers: Set<() => void> }).handlers) {\n try { handler(); } catch { /* */ }\n }\n }\n\n // ── Presence & typing ────────────────────────────────────────────────────\n\n setTyping(conversationId: string, isTyping: boolean): Promise<void> {\n return this.service.setTyping(conversationId, isTyping);\n }\n\n getTyping(conversationId: string): Promise<TypingStatusInfo> {\n return this.service.getTyping(conversationId);\n }\n\n getUnreadCount(): Promise<number> {\n return this.service.getUnreadCount();\n }\n\n // ── Preferences ──────────────────────────────────────────────────────────\n\n getPreferences(): Promise<ChatPreferences> {\n return this.service.getPreferences();\n }\n\n muteUser(username: string): Promise<void> {\n return this.service.muteUser(username);\n }\n\n unmuteUser(username: string): Promise<void> {\n return this.service.unmuteUser(username);\n }\n\n blockUser(username: string): Promise<void> {\n return this.service.blockUser(username);\n }\n\n unblockUser(username: string): Promise<void> {\n return this.service.unblockUser(username);\n }\n\n // ── Push notifications ────────────────────────────────────────────────────\n\n registerDevice(fcmToken: string): Promise<void> {\n return this.service.registerDevice(fcmToken);\n }\n\n markDmMemoFallbackSent(conversationId: string): Promise<void> {\n return this.service.markDmMemoFallbackSent(conversationId);\n }\n\n // ── Image uploads ─────────────────────────────────────────────────────────\n\n /**\n * Upload an image and get back a public URL to embed in a message.\n * After uploading, pass the URL as the message content (or append it to text).\n *\n * @example\n * const url = await client.uploadImage(file, username, challenge => keychain.sign(challenge));\n * await client.sendMessage(convId, 'dm', url);\n */\n uploadImage(\n file: File,\n username: string,\n signMessage: (message: string) => Promise<string>\n ): Promise<string> {\n return this.service.uploadImage(file, username, signMessage);\n }\n\n // ── Cleanup ───────────────────────────────────────────────────────────────\n\n destroy(): void {\n this.poller.destroy();\n this.messageCache.clear();\n }\n}\n","const IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.avif'];\n\nexport function isImageUrl(url: string): boolean {\n const trimmed = url.trim();\n try {\n const parsed = new URL(trimmed);\n if (!['http:', 'https:'].includes(parsed.protocol)) return false;\n const pathname = parsed.pathname.toLowerCase();\n return IMAGE_EXTENSIONS.some(ext => pathname.endsWith(ext));\n } catch {\n return false;\n }\n}\n\n/**\n * Extract image URLs embedded in a message's content string.\n * Snapie sends images by embedding the URL as plain text in `message.content`.\n * Use this to detect and render inline images.\n *\n * @example\n * const images = extractImageUrls(message.content);\n * images.forEach(url => console.log(<img src={url} />));\n */\nexport function extractImageUrls(content: string): string[] {\n if (!content) return [];\n const urls = content.match(/https?:\\/\\/[^\\s)]+/gi) ?? [];\n return urls.filter(isImageUrl);\n}\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -5,7 +5,8 @@ var ChatService = class {
|
|
|
5
5
|
constructor(baseUrl, storage) {
|
|
6
6
|
this.token = null;
|
|
7
7
|
this.tokenUsername = null;
|
|
8
|
-
this.
|
|
8
|
+
this.rootUrl = baseUrl.replace(/\/$/, "");
|
|
9
|
+
this.base = `${this.rootUrl}/api/chat`;
|
|
9
10
|
this.storage = storage;
|
|
10
11
|
this.token = storage.getItem(TOKEN_KEY);
|
|
11
12
|
this.tokenUsername = storage.getItem(TOKEN_USER_KEY);
|
|
@@ -169,6 +170,30 @@ var ChatService = class {
|
|
|
169
170
|
async markDmMemoFallbackSent(conversationId) {
|
|
170
171
|
await this.post(`${this.base}/dm/${conversationId}/memo-fallback`, { success: true }, true);
|
|
171
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Upload an image to the Snapie image server.
|
|
175
|
+
* `signMessage` should sign the filename string with the user's posting key
|
|
176
|
+
* (same signing function used for authentication).
|
|
177
|
+
* Returns the public URL of the uploaded image.
|
|
178
|
+
*/
|
|
179
|
+
async uploadImage(file, username, signMessage) {
|
|
180
|
+
const signature = await signMessage(file.name);
|
|
181
|
+
const form = new FormData();
|
|
182
|
+
form.append("file", file);
|
|
183
|
+
form.append("username", username);
|
|
184
|
+
form.append("signature", signature);
|
|
185
|
+
const res = await fetch(`${this.rootUrl}/api/upload-image`, {
|
|
186
|
+
method: "POST",
|
|
187
|
+
body: form
|
|
188
|
+
});
|
|
189
|
+
if (!res.ok) {
|
|
190
|
+
const err = await res.json().catch(() => ({}));
|
|
191
|
+
throw new Error(err.error ?? `HTTP ${res.status}`);
|
|
192
|
+
}
|
|
193
|
+
const data = await res.json();
|
|
194
|
+
if (!data.url) throw new Error("Upload failed: no URL returned");
|
|
195
|
+
return data.url;
|
|
196
|
+
}
|
|
172
197
|
buildQS(opts) {
|
|
173
198
|
const p = new URLSearchParams();
|
|
174
199
|
if (opts.before) p.set("before", opts.before);
|
|
@@ -455,6 +480,18 @@ var ChatClient = class {
|
|
|
455
480
|
markDmMemoFallbackSent(conversationId) {
|
|
456
481
|
return this.service.markDmMemoFallbackSent(conversationId);
|
|
457
482
|
}
|
|
483
|
+
// ── Image uploads ─────────────────────────────────────────────────────────
|
|
484
|
+
/**
|
|
485
|
+
* Upload an image and get back a public URL to embed in a message.
|
|
486
|
+
* After uploading, pass the URL as the message content (or append it to text).
|
|
487
|
+
*
|
|
488
|
+
* @example
|
|
489
|
+
* const url = await client.uploadImage(file, username, challenge => keychain.sign(challenge));
|
|
490
|
+
* await client.sendMessage(convId, 'dm', url);
|
|
491
|
+
*/
|
|
492
|
+
uploadImage(file, username, signMessage) {
|
|
493
|
+
return this.service.uploadImage(file, username, signMessage);
|
|
494
|
+
}
|
|
458
495
|
// ── Cleanup ───────────────────────────────────────────────────────────────
|
|
459
496
|
destroy() {
|
|
460
497
|
this.poller.destroy();
|
|
@@ -462,6 +499,25 @@ var ChatClient = class {
|
|
|
462
499
|
}
|
|
463
500
|
};
|
|
464
501
|
|
|
465
|
-
|
|
502
|
+
// src/utils.ts
|
|
503
|
+
var IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif"];
|
|
504
|
+
function isImageUrl(url) {
|
|
505
|
+
const trimmed = url.trim();
|
|
506
|
+
try {
|
|
507
|
+
const parsed = new URL(trimmed);
|
|
508
|
+
if (!["http:", "https:"].includes(parsed.protocol)) return false;
|
|
509
|
+
const pathname = parsed.pathname.toLowerCase();
|
|
510
|
+
return IMAGE_EXTENSIONS.some((ext) => pathname.endsWith(ext));
|
|
511
|
+
} catch {
|
|
512
|
+
return false;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
function extractImageUrls(content) {
|
|
516
|
+
if (!content) return [];
|
|
517
|
+
const urls = content.match(/https?:\/\/[^\s)]+/gi) ?? [];
|
|
518
|
+
return urls.filter(isImageUrl);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
export { ChatClient, ChatService, PollingManager, createDefaultStorage, extractImageUrls, isImageUrl };
|
|
466
522
|
//# sourceMappingURL=index.mjs.map
|
|
467
523
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/service.ts","../src/polling.ts","../src/storage.ts","../src/client.ts"],"names":["fetch"],"mappings":"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","file":"index.mjs","sourcesContent":["import type {\n Channel,\n Conversation,\n Message,\n MessagesResult,\n DmDeliveryInfo,\n TypingStatusInfo,\n ChatPreferences,\n StorageAdapter,\n} from './types';\n\nconst TOKEN_KEY = 'snapie-chat-token';\nconst TOKEN_USER_KEY = 'snapie-chat-token-user';\n\nexport class ChatService {\n private token: string | null = null;\n private tokenUsername: string | null = null;\n private base: string;\n private storage: StorageAdapter;\n\n constructor(baseUrl: string, storage: StorageAdapter) {\n this.base = `${baseUrl.replace(/\\/$/, '')}/api/chat`;\n this.storage = storage;\n this.token = storage.getItem(TOKEN_KEY);\n this.tokenUsername = storage.getItem(TOKEN_USER_KEY);\n }\n\n isAuthenticated(): boolean {\n return !!this.token;\n }\n\n getTokenUsername(): string | null {\n return this.tokenUsername;\n }\n\n async authenticate(\n username: string,\n signMessage: (msg: string) => Promise<string>\n ): Promise<void> {\n const { challenge } = await this.post<{ challenge: string }>(\n `${this.base}/auth/challenge`,\n { username },\n false\n );\n const signature = await signMessage(challenge);\n const { token } = await this.post<{ token: string }>(\n `${this.base}/auth/verify`,\n { username, challenge, signature },\n false\n );\n this.token = token;\n this.tokenUsername = username;\n this.storage.setItem(TOKEN_KEY, token);\n this.storage.setItem(TOKEN_USER_KEY, username);\n }\n\n logout(): void {\n this.token = null;\n this.tokenUsername = null;\n this.storage.removeItem(TOKEN_KEY);\n this.storage.removeItem(TOKEN_USER_KEY);\n }\n\n async getChannels(): Promise<Channel[]> {\n const { channels } = await this.get<{ channels: Channel[] }>(`${this.base}/channels`, false);\n return channels;\n }\n\n async getConversations(): Promise<Conversation[]> {\n const { conversations } = await this.get<{ conversations: Conversation[] }>(`${this.base}/conversations`, true);\n return conversations;\n }\n\n async getChannelMessages(\n channelId: string,\n opts: { before?: string; after?: string; limit?: number } = {}\n ): Promise<Message[]> {\n const qs = this.buildQS(opts);\n const { messages } = await this.get<{ messages: Message[] }>(\n `${this.base}/channels/${channelId}/messages${qs}`,\n true\n );\n return messages;\n }\n\n async sendChannelMessage(channelId: string, content: string, replyTo?: string): Promise<Message> {\n const { message } = await this.post<{ message: Message }>(\n `${this.base}/channels/${channelId}/messages`,\n { content, replyTo: replyTo ?? null },\n true\n );\n return message;\n }\n\n async editChannelMessage(channelId: string, messageId: string, content: string): Promise<Message> {\n const { message } = await this.request<{ message: Message }>(\n `${this.base}/channels/${channelId}/messages`,\n { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messageId, content }) },\n true\n );\n return message;\n }\n\n async joinChannel(channelId: string): Promise<void> {\n await this.post(`${this.base}/channels/${channelId}/join`, {}, true);\n }\n\n async leaveChannel(channelId: string): Promise<void> {\n await this.post(`${this.base}/channels/${channelId}/leave`, {}, true);\n }\n\n async getDmMessages(\n conversationId: string,\n opts: { before?: string; after?: string; limit?: number } = {}\n ): Promise<MessagesResult> {\n const qs = this.buildQS(opts);\n const { messages, status } = await this.get<MessagesResult>(\n `${this.base}/dm/${conversationId}/messages${qs}`,\n true\n );\n return { messages, status };\n }\n\n async sendDmMessage(\n conversationId: string,\n content: string,\n replyTo?: string\n ): Promise<{ message: Message; delivery?: DmDeliveryInfo }> {\n return this.post<{ message: Message; delivery?: DmDeliveryInfo }>(\n `${this.base}/dm/${conversationId}/messages`,\n { content, replyTo: replyTo ?? null },\n true\n );\n }\n\n async editDmMessage(conversationId: string, messageId: string, content: string): Promise<Message> {\n const { message } = await this.request<{ message: Message }>(\n `${this.base}/dm/${conversationId}/messages`,\n { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messageId, content }) },\n true\n );\n return message;\n }\n\n async openDm(targetUser: string): Promise<Conversation> {\n const { conversation } = await this.post<{ conversation: Conversation }>(\n `${this.base}/dm`,\n { targetUser },\n true\n );\n return conversation;\n }\n\n async createGroup(payload: {\n name: string;\n description?: string;\n isPublic?: boolean;\n members?: string[];\n }): Promise<Channel> {\n const { group } = await this.post<{ group: Channel }>(`${this.base}/groups`, payload, true);\n return group;\n }\n\n async getGroups(): Promise<Channel[]> {\n const { groups } = await this.get<{ groups: Channel[] }>(`${this.base}/groups`, true);\n return groups;\n }\n\n async addGroupMember(groupId: string, member: string): Promise<Channel> {\n const { group } = await this.post<{ group: Channel }>(\n `${this.base}/groups/${groupId}/members`,\n { member },\n true\n );\n return group;\n }\n\n async removeGroupMember(groupId: string, member: string): Promise<Channel> {\n const { group } = await this.request<{ group: Channel }>(\n `${this.base}/groups/${groupId}/members`,\n { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ member }) },\n true\n );\n return group;\n }\n\n async getUnreadCount(): Promise<number> {\n if (!this.token) return 0;\n try {\n const { unread } = await this.get<{ unread: number }>(`${this.base}/unread`, true);\n return unread;\n } catch {\n return 0;\n }\n }\n\n async setTyping(conversationId: string, isTyping: boolean): Promise<void> {\n await this.post(`${this.base}/typing`, { conversationId, isTyping }, true);\n }\n\n async getTyping(conversationId: string): Promise<TypingStatusInfo> {\n const qs = new URLSearchParams({ conversationId }).toString();\n return this.get<TypingStatusInfo>(`${this.base}/typing?${qs}`, true);\n }\n\n async getPreferences(): Promise<ChatPreferences> {\n return this.get<ChatPreferences>(`${this.base}/preferences`, true);\n }\n\n async muteUser(username: string): Promise<void> {\n await this.post(`${this.base}/preferences`, { action: 'mute', target: username }, true);\n }\n\n async unmuteUser(username: string): Promise<void> {\n await this.post(`${this.base}/preferences`, { action: 'unmute', target: username }, true);\n }\n\n async blockUser(username: string): Promise<void> {\n await this.post(`${this.base}/preferences`, { action: 'block', target: username }, true);\n }\n\n async unblockUser(username: string): Promise<void> {\n await this.post(`${this.base}/preferences`, { action: 'unblock', target: username }, true);\n }\n\n async registerDevice(fcmToken: string): Promise<void> {\n await this.post(`${this.base}/register-device`, { fcmToken }, true);\n }\n\n async markDmMemoFallbackSent(conversationId: string): Promise<void> {\n await this.post(`${this.base}/dm/${conversationId}/memo-fallback`, { success: true }, true);\n }\n\n private buildQS(opts: { before?: string; after?: string; limit?: number }): string {\n const p = new URLSearchParams();\n if (opts.before) p.set('before', opts.before);\n if (opts.after) p.set('after', opts.after);\n if (opts.limit) p.set('limit', String(opts.limit));\n const s = p.toString();\n return s ? `?${s}` : '';\n }\n\n private async get<T>(url: string, auth: boolean): Promise<T> {\n return this.request<T>(url, { method: 'GET' }, auth);\n }\n\n private async post<T>(url: string, body: unknown, auth: boolean): Promise<T> {\n return this.request<T>(\n url,\n { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) },\n auth\n );\n }\n\n async request<T>(url: string, opts: RequestInit, auth: boolean): Promise<T> {\n const headers: Record<string, string> = { ...(opts.headers as Record<string, string>) };\n if (auth && this.token) headers['Authorization'] = `Bearer ${this.token}`;\n\n const res = await fetch(url, { ...opts, headers });\n\n if (res.status === 401 && auth) {\n this.token = null;\n this.storage.removeItem(TOKEN_KEY);\n throw new Error('CHAT_UNAUTHORIZED');\n }\n\n if (!res.ok) {\n const err = await res.json().catch(() => ({}));\n throw new Error((err as { error?: string }).error ?? `HTTP ${res.status}`);\n }\n\n return res.json() as Promise<T>;\n }\n}\n","type Handler = () => void | Promise<void>;\n\n/**\n * Manages a single setInterval that fans out to multiple subscribers.\n * Starting the first subscription starts the timer; removing the last stops it.\n */\nexport class PollingManager {\n private timer: ReturnType<typeof setInterval> | null = null;\n private handlers: Set<Handler> = new Set();\n private interval: number;\n\n constructor(intervalMs: number) {\n this.interval = intervalMs;\n }\n\n subscribe(handler: Handler): () => void {\n this.handlers.add(handler);\n if (!this.timer) {\n this.timer = setInterval(() => this.tick(), this.interval);\n }\n return () => {\n this.handlers.delete(handler);\n if (this.handlers.size === 0 && this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n };\n }\n\n private tick() {\n for (const h of this.handlers) {\n try { h(); } catch { /* individual handler errors don't break others */ }\n }\n }\n\n destroy() {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n this.handlers.clear();\n }\n}\n","import type { StorageAdapter } from './types';\n\n/** Default adapter — uses localStorage when available, falls back to in-memory. */\nexport function createDefaultStorage(): StorageAdapter {\n if (typeof window !== 'undefined' && window.localStorage) {\n return window.localStorage;\n }\n const mem: Record<string, string> = {};\n return {\n getItem: (k) => mem[k] ?? null,\n setItem: (k, v) => { mem[k] = v; },\n removeItem: (k) => { delete mem[k]; },\n };\n}\n","import { ChatService } from './service';\nimport { PollingManager } from './polling';\nimport { createDefaultStorage } from './storage';\nimport type {\n ChatClientOptions,\n Channel,\n Conversation,\n Message,\n MessagesResult,\n ChatPreferences,\n TypingStatusInfo,\n DmDeliveryInfo,\n} from './types';\n\ntype ConversationsCallback = (conversations: Conversation[]) => void;\ntype MessagesCallback = (messages: Message[]) => void;\ntype UnreadCallback = (count: number) => void;\n\n/**\n * High-level Snapie chat client.\n *\n * Usage:\n * const client = new ChatClient({ baseUrl: 'https://snapie.io' });\n * await client.authenticate(username, msg => keychain.sign(msg));\n * const conversations = await client.getConversations();\n * const unsub = client.subscribeToMessages(convId, 'dm', msgs => setMessages(msgs));\n */\nexport class ChatClient {\n readonly service: ChatService;\n private poller: PollingManager;\n\n /** Cache of messages per conversationId — avoids re-rendering unchanged data */\n private messageCache: Map<string, Message[]> = new Map();\n\n constructor(options: ChatClientOptions) {\n const storage = options.storage ?? createDefaultStorage();\n this.service = new ChatService(options.baseUrl, storage);\n this.poller = new PollingManager(options.pollInterval ?? 15_000);\n }\n\n // ── Auth ─────────────────────────────────────────────────────────────────\n\n isAuthenticated(): boolean {\n return this.service.isAuthenticated();\n }\n\n getUsername(): string | null {\n return this.service.getTokenUsername();\n }\n\n /**\n * Authenticate with a Hive account.\n * `signMessage` should call Hive Keychain or equivalent with the posting key.\n */\n async authenticate(\n username: string,\n signMessage: (challenge: string) => Promise<string>\n ): Promise<void> {\n return this.service.authenticate(username, signMessage);\n }\n\n logout(): void {\n this.service.logout();\n this.messageCache.clear();\n }\n\n // ── Conversations ────────────────────────────────────────────────────────\n\n getConversations(): Promise<Conversation[]> {\n return this.service.getConversations();\n }\n\n openDm(targetUser: string): Promise<Conversation> {\n return this.service.openDm(targetUser);\n }\n\n // ── Channels ─────────────────────────────────────────────────────────────\n\n getChannels(): Promise<Channel[]> {\n return this.service.getChannels();\n }\n\n getGroups(): Promise<Channel[]> {\n return this.service.getGroups();\n }\n\n joinChannel(channelId: string): Promise<void> {\n return this.service.joinChannel(channelId);\n }\n\n leaveChannel(channelId: string): Promise<void> {\n return this.service.leaveChannel(channelId);\n }\n\n createGroup(payload: {\n name: string;\n description?: string;\n isPublic?: boolean;\n members?: string[];\n }): Promise<Channel> {\n return this.service.createGroup(payload);\n }\n\n addGroupMember(groupId: string, member: string): Promise<Channel> {\n return this.service.addGroupMember(groupId, member);\n }\n\n removeGroupMember(groupId: string, member: string): Promise<Channel> {\n return this.service.removeGroupMember(groupId, member);\n }\n\n // ── Messages ─────────────────────────────────────────────────────────────\n\n async getMessages(\n conversationId: string,\n type: 'channel' | 'dm' | 'group',\n opts: { before?: string; after?: string; limit?: number } = {}\n ): Promise<MessagesResult> {\n if (type === 'dm') {\n return this.service.getDmMessages(conversationId, opts);\n }\n const messages = await this.service.getChannelMessages(conversationId, opts);\n return { messages };\n }\n\n async sendMessage(\n conversationId: string,\n type: 'channel' | 'dm' | 'group',\n content: string,\n replyTo?: string\n ): Promise<{ message: Message; delivery?: DmDeliveryInfo }> {\n if (type === 'dm') {\n return this.service.sendDmMessage(conversationId, content, replyTo);\n }\n const message = await this.service.sendChannelMessage(conversationId, content, replyTo);\n return { message };\n }\n\n async editMessage(\n conversationId: string,\n type: 'channel' | 'dm' | 'group',\n messageId: string,\n content: string\n ): Promise<Message> {\n if (type === 'dm') {\n return this.service.editDmMessage(conversationId, messageId, content);\n }\n return this.service.editChannelMessage(conversationId, messageId, content);\n }\n\n // ── Real-time subscriptions ───────────────────────────────────────────────\n\n /**\n * Subscribe to live updates for a conversation's message list.\n * Calls `callback` immediately with the current messages, then again on every poll tick.\n * Returns an unsubscribe function.\n *\n * @example\n * const unsub = client.subscribeToMessages(conv._id, conv.type, msgs => setMessages(msgs));\n * // later:\n * unsub();\n */\n subscribeToMessages(\n conversationId: string,\n type: 'channel' | 'dm' | 'group',\n callback: MessagesCallback\n ): () => void {\n let latestMessageId: string | undefined;\n\n const fetch = async () => {\n try {\n const opts = latestMessageId ? { after: latestMessageId } : { limit: 40 };\n const { messages: incoming } = await this.getMessages(conversationId, type, opts);\n if (incoming.length === 0) return;\n\n const existing = this.messageCache.get(conversationId) ?? [];\n\n if (latestMessageId) {\n // Append only genuinely new messages\n const existingIds = new Set(existing.map(m => m._id));\n const fresh = incoming.filter(m => !existingIds.has(m._id));\n if (fresh.length === 0) return;\n const merged = [...existing, ...fresh];\n this.messageCache.set(conversationId, merged);\n callback(merged);\n } else {\n // Initial load\n this.messageCache.set(conversationId, incoming);\n callback(incoming);\n }\n\n latestMessageId = incoming[incoming.length - 1]._id;\n } catch {\n // Silently retry on next tick\n }\n };\n\n // Fire immediately, then on each poll tick\n fetch();\n const stopPolling = this.poller.subscribe(fetch);\n\n return () => {\n stopPolling();\n };\n }\n\n /**\n * Subscribe to the full conversations list, refreshed on every poll tick.\n * Returns an unsubscribe function.\n */\n subscribeToConversations(callback: ConversationsCallback): () => void {\n const fetch = async () => {\n try {\n const conversations = await this.service.getConversations();\n callback(conversations);\n } catch {\n // Silently retry on next tick\n }\n };\n\n fetch();\n return this.poller.subscribe(fetch);\n }\n\n /**\n * Subscribe to the unread message count, refreshed on every poll tick.\n * Returns an unsubscribe function.\n */\n subscribeToUnreadCount(callback: UnreadCallback): () => void {\n const fetch = async () => {\n try {\n const count = await this.service.getUnreadCount();\n callback(count);\n } catch {\n callback(0);\n }\n };\n\n fetch();\n return this.poller.subscribe(fetch);\n }\n\n /**\n * Notify the client of an incoming FCM foreground message.\n * Call this from your FCM `onMessage` handler to trigger an immediate refresh\n * rather than waiting for the next poll tick.\n *\n * @example\n * onMessage(messaging, () => client.onForegroundPush());\n */\n onForegroundPush(): void {\n for (const handler of (this.poller as unknown as { handlers: Set<() => void> }).handlers) {\n try { handler(); } catch { /* */ }\n }\n }\n\n // ── Presence & typing ────────────────────────────────────────────────────\n\n setTyping(conversationId: string, isTyping: boolean): Promise<void> {\n return this.service.setTyping(conversationId, isTyping);\n }\n\n getTyping(conversationId: string): Promise<TypingStatusInfo> {\n return this.service.getTyping(conversationId);\n }\n\n getUnreadCount(): Promise<number> {\n return this.service.getUnreadCount();\n }\n\n // ── Preferences ──────────────────────────────────────────────────────────\n\n getPreferences(): Promise<ChatPreferences> {\n return this.service.getPreferences();\n }\n\n muteUser(username: string): Promise<void> {\n return this.service.muteUser(username);\n }\n\n unmuteUser(username: string): Promise<void> {\n return this.service.unmuteUser(username);\n }\n\n blockUser(username: string): Promise<void> {\n return this.service.blockUser(username);\n }\n\n unblockUser(username: string): Promise<void> {\n return this.service.unblockUser(username);\n }\n\n // ── Push notifications ────────────────────────────────────────────────────\n\n registerDevice(fcmToken: string): Promise<void> {\n return this.service.registerDevice(fcmToken);\n }\n\n markDmMemoFallbackSent(conversationId: string): Promise<void> {\n return this.service.markDmMemoFallbackSent(conversationId);\n }\n\n // ── Cleanup ───────────────────────────────────────────────────────────────\n\n destroy(): void {\n this.poller.destroy();\n this.messageCache.clear();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/service.ts","../src/polling.ts","../src/storage.ts","../src/client.ts","../src/utils.ts"],"names":["fetch"],"mappings":"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","file":"index.mjs","sourcesContent":["import type {\n Channel,\n Conversation,\n Message,\n MessagesResult,\n DmDeliveryInfo,\n TypingStatusInfo,\n ChatPreferences,\n StorageAdapter,\n} from './types';\n\nconst TOKEN_KEY = 'snapie-chat-token';\nconst TOKEN_USER_KEY = 'snapie-chat-token-user';\n\nexport class ChatService {\n private token: string | null = null;\n private tokenUsername: string | null = null;\n private base: string;\n private rootUrl: string;\n private storage: StorageAdapter;\n\n constructor(baseUrl: string, storage: StorageAdapter) {\n this.rootUrl = baseUrl.replace(/\\/$/, '');\n this.base = `${this.rootUrl}/api/chat`;\n this.storage = storage;\n this.token = storage.getItem(TOKEN_KEY);\n this.tokenUsername = storage.getItem(TOKEN_USER_KEY);\n }\n\n isAuthenticated(): boolean {\n return !!this.token;\n }\n\n getTokenUsername(): string | null {\n return this.tokenUsername;\n }\n\n async authenticate(\n username: string,\n signMessage: (msg: string) => Promise<string>\n ): Promise<void> {\n const { challenge } = await this.post<{ challenge: string }>(\n `${this.base}/auth/challenge`,\n { username },\n false\n );\n const signature = await signMessage(challenge);\n const { token } = await this.post<{ token: string }>(\n `${this.base}/auth/verify`,\n { username, challenge, signature },\n false\n );\n this.token = token;\n this.tokenUsername = username;\n this.storage.setItem(TOKEN_KEY, token);\n this.storage.setItem(TOKEN_USER_KEY, username);\n }\n\n logout(): void {\n this.token = null;\n this.tokenUsername = null;\n this.storage.removeItem(TOKEN_KEY);\n this.storage.removeItem(TOKEN_USER_KEY);\n }\n\n async getChannels(): Promise<Channel[]> {\n const { channels } = await this.get<{ channels: Channel[] }>(`${this.base}/channels`, false);\n return channels;\n }\n\n async getConversations(): Promise<Conversation[]> {\n const { conversations } = await this.get<{ conversations: Conversation[] }>(`${this.base}/conversations`, true);\n return conversations;\n }\n\n async getChannelMessages(\n channelId: string,\n opts: { before?: string; after?: string; limit?: number } = {}\n ): Promise<Message[]> {\n const qs = this.buildQS(opts);\n const { messages } = await this.get<{ messages: Message[] }>(\n `${this.base}/channels/${channelId}/messages${qs}`,\n true\n );\n return messages;\n }\n\n async sendChannelMessage(channelId: string, content: string, replyTo?: string): Promise<Message> {\n const { message } = await this.post<{ message: Message }>(\n `${this.base}/channels/${channelId}/messages`,\n { content, replyTo: replyTo ?? null },\n true\n );\n return message;\n }\n\n async editChannelMessage(channelId: string, messageId: string, content: string): Promise<Message> {\n const { message } = await this.request<{ message: Message }>(\n `${this.base}/channels/${channelId}/messages`,\n { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messageId, content }) },\n true\n );\n return message;\n }\n\n async joinChannel(channelId: string): Promise<void> {\n await this.post(`${this.base}/channels/${channelId}/join`, {}, true);\n }\n\n async leaveChannel(channelId: string): Promise<void> {\n await this.post(`${this.base}/channels/${channelId}/leave`, {}, true);\n }\n\n async getDmMessages(\n conversationId: string,\n opts: { before?: string; after?: string; limit?: number } = {}\n ): Promise<MessagesResult> {\n const qs = this.buildQS(opts);\n const { messages, status } = await this.get<MessagesResult>(\n `${this.base}/dm/${conversationId}/messages${qs}`,\n true\n );\n return { messages, status };\n }\n\n async sendDmMessage(\n conversationId: string,\n content: string,\n replyTo?: string\n ): Promise<{ message: Message; delivery?: DmDeliveryInfo }> {\n return this.post<{ message: Message; delivery?: DmDeliveryInfo }>(\n `${this.base}/dm/${conversationId}/messages`,\n { content, replyTo: replyTo ?? null },\n true\n );\n }\n\n async editDmMessage(conversationId: string, messageId: string, content: string): Promise<Message> {\n const { message } = await this.request<{ message: Message }>(\n `${this.base}/dm/${conversationId}/messages`,\n { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messageId, content }) },\n true\n );\n return message;\n }\n\n async openDm(targetUser: string): Promise<Conversation> {\n const { conversation } = await this.post<{ conversation: Conversation }>(\n `${this.base}/dm`,\n { targetUser },\n true\n );\n return conversation;\n }\n\n async createGroup(payload: {\n name: string;\n description?: string;\n isPublic?: boolean;\n members?: string[];\n }): Promise<Channel> {\n const { group } = await this.post<{ group: Channel }>(`${this.base}/groups`, payload, true);\n return group;\n }\n\n async getGroups(): Promise<Channel[]> {\n const { groups } = await this.get<{ groups: Channel[] }>(`${this.base}/groups`, true);\n return groups;\n }\n\n async addGroupMember(groupId: string, member: string): Promise<Channel> {\n const { group } = await this.post<{ group: Channel }>(\n `${this.base}/groups/${groupId}/members`,\n { member },\n true\n );\n return group;\n }\n\n async removeGroupMember(groupId: string, member: string): Promise<Channel> {\n const { group } = await this.request<{ group: Channel }>(\n `${this.base}/groups/${groupId}/members`,\n { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ member }) },\n true\n );\n return group;\n }\n\n async getUnreadCount(): Promise<number> {\n if (!this.token) return 0;\n try {\n const { unread } = await this.get<{ unread: number }>(`${this.base}/unread`, true);\n return unread;\n } catch {\n return 0;\n }\n }\n\n async setTyping(conversationId: string, isTyping: boolean): Promise<void> {\n await this.post(`${this.base}/typing`, { conversationId, isTyping }, true);\n }\n\n async getTyping(conversationId: string): Promise<TypingStatusInfo> {\n const qs = new URLSearchParams({ conversationId }).toString();\n return this.get<TypingStatusInfo>(`${this.base}/typing?${qs}`, true);\n }\n\n async getPreferences(): Promise<ChatPreferences> {\n return this.get<ChatPreferences>(`${this.base}/preferences`, true);\n }\n\n async muteUser(username: string): Promise<void> {\n await this.post(`${this.base}/preferences`, { action: 'mute', target: username }, true);\n }\n\n async unmuteUser(username: string): Promise<void> {\n await this.post(`${this.base}/preferences`, { action: 'unmute', target: username }, true);\n }\n\n async blockUser(username: string): Promise<void> {\n await this.post(`${this.base}/preferences`, { action: 'block', target: username }, true);\n }\n\n async unblockUser(username: string): Promise<void> {\n await this.post(`${this.base}/preferences`, { action: 'unblock', target: username }, true);\n }\n\n async registerDevice(fcmToken: string): Promise<void> {\n await this.post(`${this.base}/register-device`, { fcmToken }, true);\n }\n\n async markDmMemoFallbackSent(conversationId: string): Promise<void> {\n await this.post(`${this.base}/dm/${conversationId}/memo-fallback`, { success: true }, true);\n }\n\n /**\n * Upload an image to the Snapie image server.\n * `signMessage` should sign the filename string with the user's posting key\n * (same signing function used for authentication).\n * Returns the public URL of the uploaded image.\n */\n async uploadImage(\n file: File,\n username: string,\n signMessage: (message: string) => Promise<string>\n ): Promise<string> {\n const signature = await signMessage(file.name);\n const form = new FormData();\n form.append('file', file);\n form.append('username', username);\n form.append('signature', signature);\n\n const res = await fetch(`${this.rootUrl}/api/upload-image`, {\n method: 'POST',\n body: form,\n });\n\n if (!res.ok) {\n const err = await res.json().catch(() => ({}));\n throw new Error((err as { error?: string }).error ?? `HTTP ${res.status}`);\n }\n\n const data = await res.json() as { url?: string; error?: string };\n if (!data.url) throw new Error('Upload failed: no URL returned');\n return data.url;\n }\n\n private buildQS(opts: { before?: string; after?: string; limit?: number }): string {\n const p = new URLSearchParams();\n if (opts.before) p.set('before', opts.before);\n if (opts.after) p.set('after', opts.after);\n if (opts.limit) p.set('limit', String(opts.limit));\n const s = p.toString();\n return s ? `?${s}` : '';\n }\n\n private async get<T>(url: string, auth: boolean): Promise<T> {\n return this.request<T>(url, { method: 'GET' }, auth);\n }\n\n private async post<T>(url: string, body: unknown, auth: boolean): Promise<T> {\n return this.request<T>(\n url,\n { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) },\n auth\n );\n }\n\n async request<T>(url: string, opts: RequestInit, auth: boolean): Promise<T> {\n const headers: Record<string, string> = { ...(opts.headers as Record<string, string>) };\n if (auth && this.token) headers['Authorization'] = `Bearer ${this.token}`;\n\n const res = await fetch(url, { ...opts, headers });\n\n if (res.status === 401 && auth) {\n this.token = null;\n this.storage.removeItem(TOKEN_KEY);\n throw new Error('CHAT_UNAUTHORIZED');\n }\n\n if (!res.ok) {\n const err = await res.json().catch(() => ({}));\n throw new Error((err as { error?: string }).error ?? `HTTP ${res.status}`);\n }\n\n return res.json() as Promise<T>;\n }\n}\n","type Handler = () => void | Promise<void>;\n\n/**\n * Manages a single setInterval that fans out to multiple subscribers.\n * Starting the first subscription starts the timer; removing the last stops it.\n */\nexport class PollingManager {\n private timer: ReturnType<typeof setInterval> | null = null;\n private handlers: Set<Handler> = new Set();\n private interval: number;\n\n constructor(intervalMs: number) {\n this.interval = intervalMs;\n }\n\n subscribe(handler: Handler): () => void {\n this.handlers.add(handler);\n if (!this.timer) {\n this.timer = setInterval(() => this.tick(), this.interval);\n }\n return () => {\n this.handlers.delete(handler);\n if (this.handlers.size === 0 && this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n };\n }\n\n private tick() {\n for (const h of this.handlers) {\n try { h(); } catch { /* individual handler errors don't break others */ }\n }\n }\n\n destroy() {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n this.handlers.clear();\n }\n}\n","import type { StorageAdapter } from './types';\n\n/** Default adapter — uses localStorage when available, falls back to in-memory. */\nexport function createDefaultStorage(): StorageAdapter {\n if (typeof window !== 'undefined' && window.localStorage) {\n return window.localStorage;\n }\n const mem: Record<string, string> = {};\n return {\n getItem: (k) => mem[k] ?? null,\n setItem: (k, v) => { mem[k] = v; },\n removeItem: (k) => { delete mem[k]; },\n };\n}\n","import { ChatService } from './service';\nimport { PollingManager } from './polling';\nimport { createDefaultStorage } from './storage';\nimport type {\n ChatClientOptions,\n Channel,\n Conversation,\n Message,\n MessagesResult,\n ChatPreferences,\n TypingStatusInfo,\n DmDeliveryInfo,\n} from './types';\n\ntype ConversationsCallback = (conversations: Conversation[]) => void;\ntype MessagesCallback = (messages: Message[]) => void;\ntype UnreadCallback = (count: number) => void;\n\n/**\n * High-level Snapie chat client.\n *\n * Usage:\n * const client = new ChatClient({ baseUrl: 'https://snapie.io' });\n * await client.authenticate(username, msg => keychain.sign(msg));\n * const conversations = await client.getConversations();\n * const unsub = client.subscribeToMessages(convId, 'dm', msgs => setMessages(msgs));\n */\nexport class ChatClient {\n readonly service: ChatService;\n private poller: PollingManager;\n\n /** Cache of messages per conversationId — avoids re-rendering unchanged data */\n private messageCache: Map<string, Message[]> = new Map();\n\n constructor(options: ChatClientOptions) {\n const storage = options.storage ?? createDefaultStorage();\n this.service = new ChatService(options.baseUrl, storage);\n this.poller = new PollingManager(options.pollInterval ?? 15_000);\n }\n\n // ── Auth ─────────────────────────────────────────────────────────────────\n\n isAuthenticated(): boolean {\n return this.service.isAuthenticated();\n }\n\n getUsername(): string | null {\n return this.service.getTokenUsername();\n }\n\n /**\n * Authenticate with a Hive account.\n * `signMessage` should call Hive Keychain or equivalent with the posting key.\n */\n async authenticate(\n username: string,\n signMessage: (challenge: string) => Promise<string>\n ): Promise<void> {\n return this.service.authenticate(username, signMessage);\n }\n\n logout(): void {\n this.service.logout();\n this.messageCache.clear();\n }\n\n // ── Conversations ────────────────────────────────────────────────────────\n\n getConversations(): Promise<Conversation[]> {\n return this.service.getConversations();\n }\n\n openDm(targetUser: string): Promise<Conversation> {\n return this.service.openDm(targetUser);\n }\n\n // ── Channels ─────────────────────────────────────────────────────────────\n\n getChannels(): Promise<Channel[]> {\n return this.service.getChannels();\n }\n\n getGroups(): Promise<Channel[]> {\n return this.service.getGroups();\n }\n\n joinChannel(channelId: string): Promise<void> {\n return this.service.joinChannel(channelId);\n }\n\n leaveChannel(channelId: string): Promise<void> {\n return this.service.leaveChannel(channelId);\n }\n\n createGroup(payload: {\n name: string;\n description?: string;\n isPublic?: boolean;\n members?: string[];\n }): Promise<Channel> {\n return this.service.createGroup(payload);\n }\n\n addGroupMember(groupId: string, member: string): Promise<Channel> {\n return this.service.addGroupMember(groupId, member);\n }\n\n removeGroupMember(groupId: string, member: string): Promise<Channel> {\n return this.service.removeGroupMember(groupId, member);\n }\n\n // ── Messages ─────────────────────────────────────────────────────────────\n\n async getMessages(\n conversationId: string,\n type: 'channel' | 'dm' | 'group',\n opts: { before?: string; after?: string; limit?: number } = {}\n ): Promise<MessagesResult> {\n if (type === 'dm') {\n return this.service.getDmMessages(conversationId, opts);\n }\n const messages = await this.service.getChannelMessages(conversationId, opts);\n return { messages };\n }\n\n async sendMessage(\n conversationId: string,\n type: 'channel' | 'dm' | 'group',\n content: string,\n replyTo?: string\n ): Promise<{ message: Message; delivery?: DmDeliveryInfo }> {\n if (type === 'dm') {\n return this.service.sendDmMessage(conversationId, content, replyTo);\n }\n const message = await this.service.sendChannelMessage(conversationId, content, replyTo);\n return { message };\n }\n\n async editMessage(\n conversationId: string,\n type: 'channel' | 'dm' | 'group',\n messageId: string,\n content: string\n ): Promise<Message> {\n if (type === 'dm') {\n return this.service.editDmMessage(conversationId, messageId, content);\n }\n return this.service.editChannelMessage(conversationId, messageId, content);\n }\n\n // ── Real-time subscriptions ───────────────────────────────────────────────\n\n /**\n * Subscribe to live updates for a conversation's message list.\n * Calls `callback` immediately with the current messages, then again on every poll tick.\n * Returns an unsubscribe function.\n *\n * @example\n * const unsub = client.subscribeToMessages(conv._id, conv.type, msgs => setMessages(msgs));\n * // later:\n * unsub();\n */\n subscribeToMessages(\n conversationId: string,\n type: 'channel' | 'dm' | 'group',\n callback: MessagesCallback\n ): () => void {\n let latestMessageId: string | undefined;\n\n const fetch = async () => {\n try {\n const opts = latestMessageId ? { after: latestMessageId } : { limit: 40 };\n const { messages: incoming } = await this.getMessages(conversationId, type, opts);\n if (incoming.length === 0) return;\n\n const existing = this.messageCache.get(conversationId) ?? [];\n\n if (latestMessageId) {\n // Append only genuinely new messages\n const existingIds = new Set(existing.map(m => m._id));\n const fresh = incoming.filter(m => !existingIds.has(m._id));\n if (fresh.length === 0) return;\n const merged = [...existing, ...fresh];\n this.messageCache.set(conversationId, merged);\n callback(merged);\n } else {\n // Initial load\n this.messageCache.set(conversationId, incoming);\n callback(incoming);\n }\n\n latestMessageId = incoming[incoming.length - 1]._id;\n } catch {\n // Silently retry on next tick\n }\n };\n\n // Fire immediately, then on each poll tick\n fetch();\n const stopPolling = this.poller.subscribe(fetch);\n\n return () => {\n stopPolling();\n };\n }\n\n /**\n * Subscribe to the full conversations list, refreshed on every poll tick.\n * Returns an unsubscribe function.\n */\n subscribeToConversations(callback: ConversationsCallback): () => void {\n const fetch = async () => {\n try {\n const conversations = await this.service.getConversations();\n callback(conversations);\n } catch {\n // Silently retry on next tick\n }\n };\n\n fetch();\n return this.poller.subscribe(fetch);\n }\n\n /**\n * Subscribe to the unread message count, refreshed on every poll tick.\n * Returns an unsubscribe function.\n */\n subscribeToUnreadCount(callback: UnreadCallback): () => void {\n const fetch = async () => {\n try {\n const count = await this.service.getUnreadCount();\n callback(count);\n } catch {\n callback(0);\n }\n };\n\n fetch();\n return this.poller.subscribe(fetch);\n }\n\n /**\n * Notify the client of an incoming FCM foreground message.\n * Call this from your FCM `onMessage` handler to trigger an immediate refresh\n * rather than waiting for the next poll tick.\n *\n * @example\n * onMessage(messaging, () => client.onForegroundPush());\n */\n onForegroundPush(): void {\n for (const handler of (this.poller as unknown as { handlers: Set<() => void> }).handlers) {\n try { handler(); } catch { /* */ }\n }\n }\n\n // ── Presence & typing ────────────────────────────────────────────────────\n\n setTyping(conversationId: string, isTyping: boolean): Promise<void> {\n return this.service.setTyping(conversationId, isTyping);\n }\n\n getTyping(conversationId: string): Promise<TypingStatusInfo> {\n return this.service.getTyping(conversationId);\n }\n\n getUnreadCount(): Promise<number> {\n return this.service.getUnreadCount();\n }\n\n // ── Preferences ──────────────────────────────────────────────────────────\n\n getPreferences(): Promise<ChatPreferences> {\n return this.service.getPreferences();\n }\n\n muteUser(username: string): Promise<void> {\n return this.service.muteUser(username);\n }\n\n unmuteUser(username: string): Promise<void> {\n return this.service.unmuteUser(username);\n }\n\n blockUser(username: string): Promise<void> {\n return this.service.blockUser(username);\n }\n\n unblockUser(username: string): Promise<void> {\n return this.service.unblockUser(username);\n }\n\n // ── Push notifications ────────────────────────────────────────────────────\n\n registerDevice(fcmToken: string): Promise<void> {\n return this.service.registerDevice(fcmToken);\n }\n\n markDmMemoFallbackSent(conversationId: string): Promise<void> {\n return this.service.markDmMemoFallbackSent(conversationId);\n }\n\n // ── Image uploads ─────────────────────────────────────────────────────────\n\n /**\n * Upload an image and get back a public URL to embed in a message.\n * After uploading, pass the URL as the message content (or append it to text).\n *\n * @example\n * const url = await client.uploadImage(file, username, challenge => keychain.sign(challenge));\n * await client.sendMessage(convId, 'dm', url);\n */\n uploadImage(\n file: File,\n username: string,\n signMessage: (message: string) => Promise<string>\n ): Promise<string> {\n return this.service.uploadImage(file, username, signMessage);\n }\n\n // ── Cleanup ───────────────────────────────────────────────────────────────\n\n destroy(): void {\n this.poller.destroy();\n this.messageCache.clear();\n }\n}\n","const IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.avif'];\n\nexport function isImageUrl(url: string): boolean {\n const trimmed = url.trim();\n try {\n const parsed = new URL(trimmed);\n if (!['http:', 'https:'].includes(parsed.protocol)) return false;\n const pathname = parsed.pathname.toLowerCase();\n return IMAGE_EXTENSIONS.some(ext => pathname.endsWith(ext));\n } catch {\n return false;\n }\n}\n\n/**\n * Extract image URLs embedded in a message's content string.\n * Snapie sends images by embedding the URL as plain text in `message.content`.\n * Use this to detect and render inline images.\n *\n * @example\n * const images = extractImageUrls(message.content);\n * images.forEach(url => console.log(<img src={url} />));\n */\nexport function extractImageUrls(content: string): string[] {\n if (!content) return [];\n const urls = content.match(/https?:\\/\\/[^\\s)]+/gi) ?? [];\n return urls.filter(isImageUrl);\n}\n"]}
|
package/dist/react.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { C as ChatClient, d as Conversation, M as Message } from './client-
|
|
3
|
+
import { C as ChatClient, d as Conversation, M as Message } from './client-DDdPIACK.mjs';
|
|
4
4
|
|
|
5
5
|
interface ChatProviderProps {
|
|
6
6
|
client: ChatClient;
|
package/dist/react.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { C as ChatClient, d as Conversation, M as Message } from './client-
|
|
3
|
+
import { C as ChatClient, d as Conversation, M as Message } from './client-DDdPIACK.js';
|
|
4
4
|
|
|
5
5
|
interface ChatProviderProps {
|
|
6
6
|
client: ChatClient;
|
package/package.json
CHANGED