@noverachat/sdk-web 0.0.3 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noverachat/sdk-web",
3
- "version": "0.0.3",
3
+ "version": "0.2.0",
4
4
  "description": "NoveraChat browser/Node SDK — WebSocket + REST client with optimistic UI, reconnection and gap-fill",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/client.ts CHANGED
@@ -14,6 +14,11 @@ import type {
14
14
  MessageIdStr, RegisterDeviceParams, UnreadSummary, WsAnnouncementEvent,
15
15
  WsInbound, WsPresence, WsRoomEvent,
16
16
  } from "./types.js";
17
+ import {
18
+ parseAppPolicy, parseBlockOut, parseDeviceListResponse, parseDeviceOut,
19
+ parseFileOut, parseInviteAcceptResponse, parsePushPreferencesOut,
20
+ parseUnreadSummary,
21
+ } from "./generated/rest.js";
17
22
 
18
23
  /** Client-level events (not tied to a single Room facade). `roomEvent` fires
19
24
  * for membership / room-lifecycle changes on ANY room — including ones the
@@ -202,20 +207,8 @@ export class NoveraChat {
202
207
  * read on the server) — fetch once on connect to decide which
203
208
  * destructive buttons to expose. */
204
209
  async appPolicy(): Promise<AppPolicy> {
205
- const raw = await this.http.request<{
206
- app_id: string;
207
- allow_member_bulk_delete: boolean;
208
- read_receipts_enabled: boolean;
209
- typing_indicators_enabled: boolean;
210
- allow_member_delete_any_message: boolean;
211
- }>("GET", "/api/v1/app/policy");
212
- return {
213
- appId: raw.app_id,
214
- allowMemberBulkDelete: raw.allow_member_bulk_delete,
215
- readReceiptsEnabled: raw.read_receipts_enabled ?? true,
216
- typingIndicatorsEnabled: raw.typing_indicators_enabled ?? true,
217
- deleteAnyMessageEnabled: raw.allow_member_delete_any_message ?? false,
218
- };
210
+ const raw = await this.http.request<unknown>("GET", "/api/v1/app/policy");
211
+ return parseAppPolicy(raw);
219
212
  }
220
213
 
221
214
  // ---- device tokens (push) ----
@@ -227,32 +220,21 @@ export class NoveraChat {
227
220
  * safe. Returns the stored device record (token shown only as a prefix).
228
221
  */
229
222
  async registerDevice(params: RegisterDeviceParams): Promise<DeviceInfo> {
230
- const raw = await this.http.request<{
231
- device_id: string;
232
- token_type: DeviceInfo["tokenType"];
233
- token_preview: string;
234
- os: DeviceInfo["os"];
235
- app_version: string | null;
236
- push_enabled: boolean;
237
- last_active_at: string | null;
238
- created_at: string;
239
- }>("POST", "/api/v1/users/me/devices", {
240
- device_id: params.deviceId,
241
- token: params.token,
242
- token_type: params.tokenType,
243
- os: params.os ?? null,
244
- app_version: params.appVersion ?? null,
245
- });
246
- return this._toDeviceInfo(raw);
223
+ const raw = await this.http.request<unknown>(
224
+ "POST", "/api/v1/users/me/devices", {
225
+ device_id: params.deviceId,
226
+ token: params.token,
227
+ token_type: params.tokenType,
228
+ os: params.os ?? null,
229
+ app_version: params.appVersion ?? null,
230
+ });
231
+ return parseDeviceOut(raw);
247
232
  }
248
233
 
249
234
  /** List the devices registered for the authenticated user. */
250
235
  async listDevices(): Promise<DeviceInfo[]> {
251
- const raw = await this.http.request<{
252
- items: Array<Parameters<NoveraChat["_toDeviceInfo"]>[0]>;
253
- total: number;
254
- }>("GET", "/api/v1/users/me/devices");
255
- return raw.items.map((d) => this._toDeviceInfo(d));
236
+ const raw = await this.http.request<unknown>("GET", "/api/v1/users/me/devices");
237
+ return parseDeviceListResponse(raw).items;
256
238
  }
257
239
 
258
240
  /**
@@ -288,10 +270,10 @@ export class NoveraChat {
288
270
  * 2. **Global (this)** — overrides all rooms when off.
289
271
  */
290
272
  async getPushPreferences(): Promise<{ pushEnabled: boolean }> {
291
- const raw = await this.http.request<{ push_enabled: boolean }>(
273
+ const raw = await this.http.request<unknown>(
292
274
  "GET", "/api/v1/users/me/push-preferences",
293
275
  );
294
- return { pushEnabled: Boolean(raw.push_enabled) };
276
+ return parsePushPreferencesOut(raw);
295
277
  }
296
278
 
297
279
  /**
@@ -300,11 +282,11 @@ export class NoveraChat {
300
282
  * `true` to restore normal delivery. Idempotent.
301
283
  */
302
284
  async setPushEnabled(enabled: boolean): Promise<{ pushEnabled: boolean }> {
303
- const raw = await this.http.request<{ push_enabled: boolean }>(
285
+ const raw = await this.http.request<unknown>(
304
286
  "PATCH", "/api/v1/users/me/push-preferences",
305
287
  { push_enabled: enabled },
306
288
  );
307
- return { pushEnabled: Boolean(raw.push_enabled) };
289
+ return parsePushPreferencesOut(raw);
308
290
  }
309
291
 
310
292
  // -------------------------------------------------------------------------
@@ -325,19 +307,11 @@ export class NoveraChat {
325
307
  * @param reason Optional local note ≤500 chars.
326
308
  */
327
309
  async blockUser(userId: string, reason?: string): Promise<Block> {
328
- const raw = await this.http.request<{
329
- blocked_user_id: string;
330
- reason: string | null;
331
- created_at: string;
332
- }>(
310
+ const raw = await this.http.request<unknown>(
333
311
  "POST", `/api/v1/users/me/blocks/${encodeURIComponent(userId)}`,
334
312
  { reason: reason ?? null },
335
313
  );
336
- return {
337
- blockedUserId: raw.blocked_user_id,
338
- reason: raw.reason,
339
- createdAt: raw.created_at,
340
- };
314
+ return parseBlockOut(raw);
341
315
  }
342
316
 
343
317
  /** Unblock a user. Idempotent — resolves either way. */
@@ -349,38 +323,8 @@ export class NoveraChat {
349
323
 
350
324
  /** List every user the caller has blocked, newest first. */
351
325
  async listBlocks(): Promise<Block[]> {
352
- const raw = await this.http.request<Array<{
353
- blocked_user_id: string;
354
- reason: string | null;
355
- created_at: string;
356
- }>>("GET", "/api/v1/users/me/blocks");
357
- return raw.map((r) => ({
358
- blockedUserId: r.blocked_user_id,
359
- reason: r.reason,
360
- createdAt: r.created_at,
361
- }));
362
- }
363
-
364
- private _toDeviceInfo(raw: {
365
- device_id: string;
366
- token_type: DeviceInfo["tokenType"];
367
- token_preview: string;
368
- os: DeviceInfo["os"];
369
- app_version: string | null;
370
- push_enabled: boolean;
371
- last_active_at: string | null;
372
- created_at: string;
373
- }): DeviceInfo {
374
- return {
375
- deviceId: raw.device_id,
376
- tokenType: raw.token_type,
377
- tokenPreview: raw.token_preview,
378
- os: raw.os,
379
- appVersion: raw.app_version,
380
- pushEnabled: raw.push_enabled,
381
- lastActiveAt: raw.last_active_at,
382
- createdAt: raw.created_at,
383
- };
326
+ const raw = await this.http.request<unknown[]>("GET", "/api/v1/users/me/blocks");
327
+ return raw.map((r) => parseBlockOut(r));
384
328
  }
385
329
 
386
330
  /**
@@ -441,76 +385,31 @@ export class NoveraChat {
441
385
  * shipping date.
442
386
  */
443
387
  async getFileMeta(fileId: MessageIdStr): Promise<FileMeta> {
444
- // Wire shape mirrors backend ``FileOut`` (Pydantic v2). The ``id``
445
- // field comes through with its alias name, not the model attribute
446
- // ``file_id`` easy mistake to make when looking at the SQLAlchemy
447
- // model. ``original_name`` was renamed to ``name`` in the inline
448
- // shape but the standalone meta still uses ``original_name``.
449
- const raw = await this.http.request<{
450
- id: MessageIdStr;
451
- kind: "image" | "video" | "file";
452
- mime: string;
453
- size_bytes: number;
454
- original_name: string | null;
455
- width: number | null;
456
- height: number | null;
457
- duration_sec: number | null;
458
- thumbnail_status: "pending" | "ready" | "failed" | "skipped";
459
- forward_blocked?: boolean;
460
- }>("GET", `/api/v1/files/${encodeURIComponent(fileId)}/meta`);
388
+ // Endpoint returns the generated ``FileOut`` shape (size_bytes /
389
+ // original_name / …). Parse it with the codegen'd converter, then
390
+ // fold down to the leaner inline ``FileMeta`` (= FileInline) the SDK
391
+ // exposes the same fields WS chat_receive frames carry.
392
+ const raw = await this.http.request<unknown>(
393
+ "GET", `/api/v1/files/${encodeURIComponent(fileId)}/meta`,
394
+ );
395
+ const f = parseFileOut(raw);
461
396
  return {
462
- id: raw.id,
463
- kind: raw.kind,
464
- mime: raw.mime,
465
- size: raw.size_bytes,
466
- name: raw.original_name ?? null,
467
- width: raw.width ?? null,
468
- height: raw.height ?? null,
469
- durationSec: raw.duration_sec ?? null,
470
- thumbnailStatus: raw.thumbnail_status,
471
- forwardBlocked: Boolean(raw.forward_blocked),
397
+ id: f.id,
398
+ kind: f.kind,
399
+ mime: f.mime,
400
+ size: f.sizeBytes,
401
+ name: f.originalName ?? null,
402
+ width: f.width ?? null,
403
+ height: f.height ?? null,
404
+ durationSec: f.durationSec ?? null,
405
+ thumbnailStatus: f.thumbnailStatus,
406
+ forwardBlocked: Boolean(f.forwardBlocked),
472
407
  };
473
408
  }
474
409
 
475
410
  async unreadSummary(): Promise<UnreadSummary> {
476
- const raw = await this.http.request<{
477
- total: number;
478
- rooms: Array<{
479
- room_id: string;
480
- name: string | null;
481
- room_type: string | null;
482
- unread_count: number;
483
- last_message_id: string | null;
484
- last_message_preview: string | null;
485
- last_read_message_id: string | null;
486
- member_count: number;
487
- members_preview: Array<{
488
- user_id: string;
489
- nickname: string | null;
490
- profile_image_url: string | null;
491
- is_online: boolean | null;
492
- }>;
493
- }>;
494
- }>("GET", "/api/v1/users/me/unread");
495
- return {
496
- total: raw.total,
497
- rooms: raw.rooms.map((r) => ({
498
- roomId: r.room_id,
499
- name: r.name,
500
- roomType: r.room_type ?? null,
501
- unreadCount: r.unread_count,
502
- lastMessageId: r.last_message_id,
503
- lastMessagePreview: r.last_message_preview ?? null,
504
- lastReadMessageId: r.last_read_message_id,
505
- memberCount: r.member_count ?? 0,
506
- members: (r.members_preview ?? []).map((m) => ({
507
- userId: m.user_id,
508
- nickname: m.nickname ?? null,
509
- profileImageUrl: m.profile_image_url ?? null,
510
- isOnline: m.is_online ?? null,
511
- })),
512
- })),
513
- };
411
+ const raw = await this.http.request<unknown>("GET", "/api/v1/users/me/unread");
412
+ return parseUnreadSummary(raw);
514
413
  }
515
414
 
516
415
  /**
@@ -529,16 +428,10 @@ export class NoveraChat {
529
428
  * kicked from the target room.
530
429
  */
531
430
  async acceptInvite(token: string): Promise<AcceptInviteResult> {
532
- const raw = await this.http.request<{
533
- room_id: string;
534
- room_name: string | null;
535
- was_already_member: boolean;
536
- }>("POST", `/api/v1/invites/${encodeURIComponent(token)}/accept`);
537
- return {
538
- roomId: raw.room_id,
539
- roomName: raw.room_name,
540
- wasAlreadyMember: raw.was_already_member,
541
- };
431
+ const raw = await this.http.request<unknown>(
432
+ "POST", `/api/v1/invites/${encodeURIComponent(token)}/accept`,
433
+ );
434
+ return parseInviteAcceptResponse(raw);
542
435
  }
543
436
 
544
437
  // ---- internal ----