@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 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-Bztyx_3e.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-Bztyx_3e.mjs';
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
- export { PollingManager, StorageAdapter, createDefaultStorage };
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-Bztyx_3e.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-Bztyx_3e.js';
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
- export { PollingManager, StorageAdapter, createDefaultStorage };
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.base = `${baseUrl.replace(/\/$/, "")}/api/chat`;
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.base = `${baseUrl.replace(/\/$/, "")}/api/chat`;
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
- export { ChatClient, ChatService, PollingManager, createDefaultStorage };
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
@@ -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-Bztyx_3e.mjs';
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-Bztyx_3e.js';
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snapie/chat-client",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Hive-authenticated chat client SDK for Snapie — DMs, channels, groups, and real-time subscriptions",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",