@poolse/sdk 0.2.0-alpha.7 → 1.0.0-rc.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/CHANGELOG.md +9 -0
- package/README.md +3 -1
- package/dist/index.cjs +151 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +235 -117
- package/dist/index.d.ts +235 -117
- package/dist/index.js +151 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,120 +1,5 @@
|
|
|
1
1
|
import { Channel } from 'phoenix';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Hosted poolse API URL. Used as the default for `PoolseConfig.apiUrl`
|
|
5
|
-
* when you don't pass one — appropriate for the vast majority of
|
|
6
|
-
* integrations that target the official poolse cloud. Self-hosted /
|
|
7
|
-
* staging deployments override via the `apiUrl` field.
|
|
8
|
-
*/
|
|
9
|
-
declare const POOLSE_API_URL = "https://api.poolse.dev";
|
|
10
|
-
/**
|
|
11
|
-
* SDK configuration passed to `new Poolse(config)`.
|
|
12
|
-
*/
|
|
13
|
-
interface PoolseConfig {
|
|
14
|
-
/**
|
|
15
|
-
* Base URL of the poolse REST API. Defaults to the hosted endpoint
|
|
16
|
-
* at `https://api.poolse.dev`. Override only for self-hosted /
|
|
17
|
-
* staging deployments. MUST NOT include the `/v1` path — the SDK
|
|
18
|
-
* adds that itself.
|
|
19
|
-
*/
|
|
20
|
-
apiUrl?: string;
|
|
21
|
-
/**
|
|
22
|
-
* Async hook the SDK calls every time it needs an `Authorization:
|
|
23
|
-
* Bearer <jwt>` header. Most apps refresh the JWT from their own
|
|
24
|
-
* backend here — the SDK never talks to poolse's `POST
|
|
25
|
-
* /v1/users/:user_id/tokens` itself (that endpoint is API-key-authed
|
|
26
|
-
* and lives on the Customer's BACKEND, not the End User's device).
|
|
27
|
-
*
|
|
28
|
-
* Return `null` to deliberately make an unauthenticated request — the
|
|
29
|
-
* server will reject it, but the SDK won't error inside `getToken`.
|
|
30
|
-
*/
|
|
31
|
-
getToken: () => Promise<string | null> | string | null;
|
|
32
|
-
/**
|
|
33
|
-
* Optional fetch override. Browsers and Node 22+ both ship a global
|
|
34
|
-
* `fetch`, but tests can inject a mock here; bundlers in restricted
|
|
35
|
-
* environments can supply a polyfill.
|
|
36
|
-
*/
|
|
37
|
-
fetch?: typeof globalThis.fetch;
|
|
38
|
-
/**
|
|
39
|
-
* Retry budget for transient failures (network + 5xx + 429). Defaults
|
|
40
|
-
* to 3 attempts after the initial request. Set to 0 to disable.
|
|
41
|
-
*/
|
|
42
|
-
maxRetries?: number;
|
|
43
|
-
/**
|
|
44
|
-
* Base for the exponential backoff, in milliseconds. Each retry waits
|
|
45
|
-
* `min(maxBackoffMs, baseBackoffMs * 2^attempt)` plus jitter, OR honours
|
|
46
|
-
* the `Retry-After` header if present. Default 250 ms.
|
|
47
|
-
*/
|
|
48
|
-
baseBackoffMs?: number;
|
|
49
|
-
/** Hard cap on a single retry delay. Default 30_000 ms. */
|
|
50
|
-
maxBackoffMs?: number;
|
|
51
|
-
/**
|
|
52
|
-
* Override the idempotency-key generator. Defaults to
|
|
53
|
-
* `crypto.randomUUID()`. Most apps don't need to override this — the
|
|
54
|
-
* generator is exposed mainly for deterministic tests.
|
|
55
|
-
*/
|
|
56
|
-
generateIdempotencyKey?: () => string;
|
|
57
|
-
/**
|
|
58
|
-
* Override the WebSocket URL. Defaults to `apiUrl` with `http(s)://`
|
|
59
|
-
* swapped to `ws(s)://`, suitable when the realtime gateway shares
|
|
60
|
-
* its origin with the REST API. Set explicitly for split-host
|
|
61
|
-
* deployments (`https://api.example.com` REST + `wss://realtime.example.com` WS).
|
|
62
|
-
*/
|
|
63
|
-
wsUrl?: string;
|
|
64
|
-
/**
|
|
65
|
-
* Path the WebSocket is mounted on. Defaults to `/socket` — matches
|
|
66
|
-
* `CaasRealtimeWeb.UserSocket`'s mount point.
|
|
67
|
-
*/
|
|
68
|
-
socketPath?: string;
|
|
69
|
-
/**
|
|
70
|
-
* Called when the underlying socket encounters a non-fatal error
|
|
71
|
-
* (Phoenix retries internally). Useful for surfacing reconnect
|
|
72
|
-
* banners in the UI without coupling to socket internals.
|
|
73
|
-
*/
|
|
74
|
-
onSocketError?: (err: Error) => void;
|
|
75
|
-
}
|
|
76
|
-
/** Internal resolved config — all the defaults filled in. */
|
|
77
|
-
interface ResolvedConfig {
|
|
78
|
-
apiUrl: string;
|
|
79
|
-
getToken: PoolseConfig['getToken'];
|
|
80
|
-
fetch: typeof globalThis.fetch;
|
|
81
|
-
maxRetries: number;
|
|
82
|
-
baseBackoffMs: number;
|
|
83
|
-
maxBackoffMs: number;
|
|
84
|
-
generateIdempotencyKey: () => string;
|
|
85
|
-
wsUrl: string | undefined;
|
|
86
|
-
socketPath: string;
|
|
87
|
-
onSocketError: ((err: Error) => void) | undefined;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
type Fetcher = PoolseConfig['getToken'];
|
|
91
|
-
interface GetTokenOptions {
|
|
92
|
-
/** Bypass the cache and force a fresh call to the consumer's `getToken`. */
|
|
93
|
-
forceRefresh?: boolean;
|
|
94
|
-
}
|
|
95
|
-
declare class TokenCache {
|
|
96
|
-
private readonly fetcher;
|
|
97
|
-
private token;
|
|
98
|
-
private expMs;
|
|
99
|
-
private inFlight;
|
|
100
|
-
constructor(fetcher: Fetcher);
|
|
101
|
-
/**
|
|
102
|
-
* Synchronously return the cached token without triggering a fetch.
|
|
103
|
-
* Returns `null` if the cache is empty OR if the cached token is
|
|
104
|
-
* within the refresh window (treating near-expiry tokens as stale
|
|
105
|
-
* keeps the realtime layer from handshaking with an about-to-expire
|
|
106
|
-
* JWT when a refresh is already due).
|
|
107
|
-
*
|
|
108
|
-
* Exists for callers like Phoenix.js's `params` callback that the
|
|
109
|
-
* library invokes synchronously and does NOT await — see
|
|
110
|
-
* `phoenix/priv/static/phoenix.mjs::endPointURL()`.
|
|
111
|
-
*/
|
|
112
|
-
peekToken(): string | null;
|
|
113
|
-
getToken(opts?: GetTokenOptions): Promise<string | null>;
|
|
114
|
-
invalidate(): void;
|
|
115
|
-
private fetchAndStore;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
3
|
type Uuid = string;
|
|
119
4
|
type IsoDateTime = string;
|
|
120
5
|
interface Me {
|
|
@@ -262,6 +147,30 @@ interface Message {
|
|
|
262
147
|
inserted_at: IsoDateTime;
|
|
263
148
|
updated_at: IsoDateTime;
|
|
264
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Customer-supplied user metadata — the SDK doesn't know your users'
|
|
152
|
+
* names or where their avatars live. Customers wire a
|
|
153
|
+
* `PoolseConfig.userResolver` that maps a poolse `Uuid` to whatever
|
|
154
|
+
* their app already stores: a display name, an avatar URL.
|
|
155
|
+
*
|
|
156
|
+
* The SDK caches resolved profiles in-memory (deduplicating
|
|
157
|
+
* concurrent lookups) so a 50-message render only fires one
|
|
158
|
+
* resolver call per unique sender.
|
|
159
|
+
*/
|
|
160
|
+
interface PoolseUserProfile {
|
|
161
|
+
/**
|
|
162
|
+
* The name shown in sender labels, mention dropdowns, member
|
|
163
|
+
* lists, and read-receipt tooltips. Customers usually pass their
|
|
164
|
+
* app's `display_name` / `username` / `full_name`.
|
|
165
|
+
*/
|
|
166
|
+
displayName: string;
|
|
167
|
+
/**
|
|
168
|
+
* Square avatar URL (any size — the UI scales). Null = render the
|
|
169
|
+
* fallback `<Avatar>` with initials. Optional so customers that
|
|
170
|
+
* don't track avatars can omit the field.
|
|
171
|
+
*/
|
|
172
|
+
avatarUrl?: string | null;
|
|
173
|
+
}
|
|
265
174
|
/**
|
|
266
175
|
* Compact preview of a quoted message, embedded on the quoting
|
|
267
176
|
* message when `Message.quoted_message_id` is set. Just enough for
|
|
@@ -368,6 +277,150 @@ interface ErrorEnvelope {
|
|
|
368
277
|
};
|
|
369
278
|
}
|
|
370
279
|
|
|
280
|
+
/**
|
|
281
|
+
* Hosted poolse API URL. Used as the default for `PoolseConfig.apiUrl`
|
|
282
|
+
* when you don't pass one — appropriate for the vast majority of
|
|
283
|
+
* integrations that target the official poolse cloud. Self-hosted /
|
|
284
|
+
* staging deployments override via the `apiUrl` field.
|
|
285
|
+
*/
|
|
286
|
+
declare const POOLSE_API_URL = "https://api.poolse.dev";
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* SDK configuration passed to `new Poolse(config)`.
|
|
290
|
+
*/
|
|
291
|
+
interface PoolseConfig {
|
|
292
|
+
/**
|
|
293
|
+
* Base URL of the poolse REST API. Defaults to the hosted endpoint
|
|
294
|
+
* at `https://api.poolse.dev`. Override only for self-hosted /
|
|
295
|
+
* staging deployments. MUST NOT include the `/v1` path — the SDK
|
|
296
|
+
* adds that itself.
|
|
297
|
+
*/
|
|
298
|
+
apiUrl?: string;
|
|
299
|
+
/**
|
|
300
|
+
* Async hook the SDK calls every time it needs an `Authorization:
|
|
301
|
+
* Bearer <jwt>` header. Most apps refresh the JWT from their own
|
|
302
|
+
* backend here — the SDK never talks to poolse's `POST
|
|
303
|
+
* /v1/users/:user_id/tokens` itself (that endpoint is API-key-authed
|
|
304
|
+
* and lives on the Customer's BACKEND, not the End User's device).
|
|
305
|
+
*
|
|
306
|
+
* Return `null` to deliberately make an unauthenticated request — the
|
|
307
|
+
* server will reject it, but the SDK won't error inside `getToken`.
|
|
308
|
+
*/
|
|
309
|
+
getToken: () => Promise<string | null> | string | null;
|
|
310
|
+
/**
|
|
311
|
+
* Optional fetch override. Browsers and Node 22+ both ship a global
|
|
312
|
+
* `fetch`, but tests can inject a mock here; bundlers in restricted
|
|
313
|
+
* environments can supply a polyfill.
|
|
314
|
+
*/
|
|
315
|
+
fetch?: typeof globalThis.fetch;
|
|
316
|
+
/**
|
|
317
|
+
* Retry budget for transient failures (network + 5xx + 429). Defaults
|
|
318
|
+
* to 3 attempts after the initial request. Set to 0 to disable.
|
|
319
|
+
*/
|
|
320
|
+
maxRetries?: number;
|
|
321
|
+
/**
|
|
322
|
+
* Base for the exponential backoff, in milliseconds. Each retry waits
|
|
323
|
+
* `min(maxBackoffMs, baseBackoffMs * 2^attempt)` plus jitter, OR honours
|
|
324
|
+
* the `Retry-After` header if present. Default 250 ms.
|
|
325
|
+
*/
|
|
326
|
+
baseBackoffMs?: number;
|
|
327
|
+
/** Hard cap on a single retry delay. Default 30_000 ms. */
|
|
328
|
+
maxBackoffMs?: number;
|
|
329
|
+
/**
|
|
330
|
+
* Override the idempotency-key generator. Defaults to
|
|
331
|
+
* `crypto.randomUUID()`. Most apps don't need to override this — the
|
|
332
|
+
* generator is exposed mainly for deterministic tests.
|
|
333
|
+
*/
|
|
334
|
+
generateIdempotencyKey?: () => string;
|
|
335
|
+
/**
|
|
336
|
+
* Override the WebSocket URL. Defaults to `apiUrl` with `http(s)://`
|
|
337
|
+
* swapped to `ws(s)://`, suitable when the realtime gateway shares
|
|
338
|
+
* its origin with the REST API. Set explicitly for split-host
|
|
339
|
+
* deployments (`https://api.example.com` REST + `wss://realtime.example.com` WS).
|
|
340
|
+
*/
|
|
341
|
+
wsUrl?: string;
|
|
342
|
+
/**
|
|
343
|
+
* Path the WebSocket is mounted on. Defaults to `/socket` — matches
|
|
344
|
+
* `CaasRealtimeWeb.UserSocket`'s mount point.
|
|
345
|
+
*/
|
|
346
|
+
socketPath?: string;
|
|
347
|
+
/**
|
|
348
|
+
* Called when the underlying socket encounters a non-fatal error
|
|
349
|
+
* (Phoenix retries internally). Useful for surfacing reconnect
|
|
350
|
+
* banners in the UI without coupling to socket internals.
|
|
351
|
+
*/
|
|
352
|
+
onSocketError?: (err: Error) => void;
|
|
353
|
+
/**
|
|
354
|
+
* Resolve a poolse `user_id` to the customer's own user metadata
|
|
355
|
+
* (display name + avatar). Called by `chat.users.get(userId)` and
|
|
356
|
+
* the `useUser(userId)` React hook whenever a UI component needs
|
|
357
|
+
* to render a participant.
|
|
358
|
+
*
|
|
359
|
+
* The SDK caches results in-memory and dedupes concurrent calls,
|
|
360
|
+
* so a busy chat with 50 messages from 5 senders fires the
|
|
361
|
+
* resolver 5 times — once per unique sender — not 50.
|
|
362
|
+
*
|
|
363
|
+
* Customers typically hit their OWN backend here:
|
|
364
|
+
*
|
|
365
|
+
* userResolver: async (userId) => {
|
|
366
|
+
* const u = await fetch(`/api/users/by-poolse-id/${userId}`)
|
|
367
|
+
* .then((r) => r.json());
|
|
368
|
+
* return { displayName: u.full_name, avatarUrl: u.avatar_url };
|
|
369
|
+
* }
|
|
370
|
+
*
|
|
371
|
+
* Sync returns are fine when the customer already has the user
|
|
372
|
+
* data in memory (e.g., from a directory loaded at app boot):
|
|
373
|
+
*
|
|
374
|
+
* userResolver: (userId) => directory[userId] ?? null
|
|
375
|
+
*
|
|
376
|
+
* Return `null` when the user can't be found — components fall
|
|
377
|
+
* back to a userId-derived label and an initials avatar.
|
|
378
|
+
*/
|
|
379
|
+
userResolver?: (userId: string) => Promise<PoolseUserProfile | null> | PoolseUserProfile | null;
|
|
380
|
+
}
|
|
381
|
+
/** Internal resolved config — all the defaults filled in. */
|
|
382
|
+
interface ResolvedConfig {
|
|
383
|
+
apiUrl: string;
|
|
384
|
+
getToken: PoolseConfig['getToken'];
|
|
385
|
+
fetch: typeof globalThis.fetch;
|
|
386
|
+
maxRetries: number;
|
|
387
|
+
baseBackoffMs: number;
|
|
388
|
+
maxBackoffMs: number;
|
|
389
|
+
generateIdempotencyKey: () => string;
|
|
390
|
+
wsUrl: string | undefined;
|
|
391
|
+
socketPath: string;
|
|
392
|
+
onSocketError: ((err: Error) => void) | undefined;
|
|
393
|
+
userResolver: PoolseConfig['userResolver'];
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
type Fetcher = PoolseConfig['getToken'];
|
|
397
|
+
interface GetTokenOptions {
|
|
398
|
+
/** Bypass the cache and force a fresh call to the consumer's `getToken`. */
|
|
399
|
+
forceRefresh?: boolean;
|
|
400
|
+
}
|
|
401
|
+
declare class TokenCache {
|
|
402
|
+
private readonly fetcher;
|
|
403
|
+
private token;
|
|
404
|
+
private expMs;
|
|
405
|
+
private inFlight;
|
|
406
|
+
constructor(fetcher: Fetcher);
|
|
407
|
+
/**
|
|
408
|
+
* Synchronously return the cached token without triggering a fetch.
|
|
409
|
+
* Returns `null` if the cache is empty OR if the cached token is
|
|
410
|
+
* within the refresh window (treating near-expiry tokens as stale
|
|
411
|
+
* keeps the realtime layer from handshaking with an about-to-expire
|
|
412
|
+
* JWT when a refresh is already due).
|
|
413
|
+
*
|
|
414
|
+
* Exists for callers like Phoenix.js's `params` callback that the
|
|
415
|
+
* library invokes synchronously and does NOT await — see
|
|
416
|
+
* `phoenix/priv/static/phoenix.mjs::endPointURL()`.
|
|
417
|
+
*/
|
|
418
|
+
peekToken(): string | null;
|
|
419
|
+
getToken(opts?: GetTokenOptions): Promise<string | null>;
|
|
420
|
+
invalidate(): void;
|
|
421
|
+
private fetchAndStore;
|
|
422
|
+
}
|
|
423
|
+
|
|
371
424
|
/** `message:new` / `message:updated` push payloads. */
|
|
372
425
|
type MessageNewEvent = Message;
|
|
373
426
|
type MessageUpdatedEvent = Message;
|
|
@@ -612,6 +665,23 @@ interface AttachmentUploadInput {
|
|
|
612
665
|
/** Options accepted by every attachment method. */
|
|
613
666
|
interface AttachmentOptions {
|
|
614
667
|
signal?: AbortSignal;
|
|
668
|
+
/**
|
|
669
|
+
* Progress callback for `upload()`. Called periodically during the
|
|
670
|
+
* PUT phase (NOT during the presigned-URL request — that's a small
|
|
671
|
+
* JSON round-trip). When set, the SDK switches to XHR for the PUT
|
|
672
|
+
* since the standard `fetch` doesn't expose upload progress events.
|
|
673
|
+
*
|
|
674
|
+
* Customers using a custom `config.fetch` (e.g. node-fetch polyfill)
|
|
675
|
+
* lose progress reporting and the callback never fires — XHR is
|
|
676
|
+
* a browser-only API.
|
|
677
|
+
*/
|
|
678
|
+
onProgress?: (event: AttachmentProgressEvent) => void;
|
|
679
|
+
}
|
|
680
|
+
interface AttachmentProgressEvent {
|
|
681
|
+
/** Bytes uploaded so far. */
|
|
682
|
+
loaded: number;
|
|
683
|
+
/** Total bytes — equals `input.byteSize` (passed back for convenience). */
|
|
684
|
+
total: number;
|
|
615
685
|
}
|
|
616
686
|
/** Top-level `/v1/attachments` collection. */
|
|
617
687
|
declare class AttachmentsResource {
|
|
@@ -794,6 +864,43 @@ declare class MeResource {
|
|
|
794
864
|
show(signal?: AbortSignal): Promise<Me>;
|
|
795
865
|
}
|
|
796
866
|
|
|
867
|
+
type Listener = () => void;
|
|
868
|
+
declare class UsersResource {
|
|
869
|
+
private readonly config;
|
|
870
|
+
private readonly cache;
|
|
871
|
+
private readonly pending;
|
|
872
|
+
private readonly listeners;
|
|
873
|
+
constructor(config: ResolvedConfig);
|
|
874
|
+
/**
|
|
875
|
+
* Get the cached value if present. Returns `undefined` to mean
|
|
876
|
+
* "not in cache yet" (different from `null`, which means "resolver
|
|
877
|
+
* ran and the user wasn't found").
|
|
878
|
+
*/
|
|
879
|
+
peek(userId: string): PoolseUserProfile | null | undefined;
|
|
880
|
+
/**
|
|
881
|
+
* Resolve a user, hitting the customer's `userResolver` on cache
|
|
882
|
+
* miss. Concurrent calls for the same id share one Promise.
|
|
883
|
+
*/
|
|
884
|
+
get(userId: string): Promise<PoolseUserProfile | null>;
|
|
885
|
+
/**
|
|
886
|
+
* Subscribe to changes for a single user id. The listener fires
|
|
887
|
+
* when the resolver lands (or when the entry is invalidated).
|
|
888
|
+
* Returns an unsubscribe.
|
|
889
|
+
*
|
|
890
|
+
* `useUser` in @poolse/react uses this with `useSyncExternalStore`.
|
|
891
|
+
*/
|
|
892
|
+
subscribe(userId: string, listener: Listener): () => void;
|
|
893
|
+
/** Drop a single cached entry — next `get` re-fetches via the resolver. */
|
|
894
|
+
invalidate(userId: string): void;
|
|
895
|
+
/**
|
|
896
|
+
* Drop the entire cache. Use after a sign-out, tenant swap, or any
|
|
897
|
+
* other event that invalidates every cached profile (e.g., the
|
|
898
|
+
* customer just renamed every user in bulk).
|
|
899
|
+
*/
|
|
900
|
+
invalidateAll(): void;
|
|
901
|
+
private notify;
|
|
902
|
+
}
|
|
903
|
+
|
|
797
904
|
declare class Poolse {
|
|
798
905
|
/** `/v1/me` — current End User. */
|
|
799
906
|
readonly me: MeResource;
|
|
@@ -803,6 +910,17 @@ declare class Poolse {
|
|
|
803
910
|
readonly messages: MessagesResource;
|
|
804
911
|
/** `/v1/attachments/*` — presigned-URL uploads/downloads. */
|
|
805
912
|
readonly attachments: AttachmentsResource;
|
|
913
|
+
/**
|
|
914
|
+
* Customer-supplied user metadata, cached + dedup'd.
|
|
915
|
+
* `chat.users.get(userId)` returns `{ displayName, avatarUrl }`
|
|
916
|
+
* via the optional `config.userResolver`. UI components
|
|
917
|
+
* (`MessageBubble`, `MemberList`, `TypingIndicator`) pick this up
|
|
918
|
+
* automatically via the `useUser` hook in `@poolse/react`.
|
|
919
|
+
*
|
|
920
|
+
* If no resolver is configured, `get` always returns `null` and
|
|
921
|
+
* UI falls back to the userId slice + initials avatar.
|
|
922
|
+
*/
|
|
923
|
+
readonly users: UsersResource;
|
|
806
924
|
/**
|
|
807
925
|
* Low-level REST client. Exposed for advanced use cases (custom endpoints,
|
|
808
926
|
* raw retry/headers control). Most callers should use the resources above.
|
|
@@ -871,6 +989,6 @@ declare class AuthError extends ApiError {
|
|
|
871
989
|
constructor(envelope: ErrorEnvelope['error']);
|
|
872
990
|
}
|
|
873
991
|
|
|
874
|
-
declare const version = "0.0.
|
|
992
|
+
declare const version = "1.0.0-rc.0";
|
|
875
993
|
|
|
876
|
-
export { type AddMemberOptions, ApiError, type Attachment, type AttachmentDownloadResponse, AttachmentHandle, type AttachmentOptions, type AttachmentStatus, type AttachmentUploadInput, type AttachmentUploadRequest, type AttachmentUploadResponse, AttachmentsResource, AuthError, type Conversation, ConversationChannel, type ConversationCreateRequest, type ConversationCreatedEvent, ConversationHandle, type ConversationList, ConversationMessages, type ConversationType, type ConversationUpdateRequest, ConversationsResource, type ErrorEnvelope, type IsoDateTime, type Me, MeResource, type MemberReadEvent, type MemberRole, type Membership, type MembershipCreateRequest, type MembershipList, type MentionEvent, type Message, type MessageCreateRequest, type MessageDeletedEvent, MessageHandle, type MessageList, type MessageNewEvent, type MessageType, type MessageUpdateRequest, type MessageUpdatedEvent, MessagesResource, NetworkError, POOLSE_API_URL, Poolse, type PoolseConfig, PoolseError, PoolseRealtime, type PresenceSnapshot, type QuotedMessagePreview, RateLimitedError, type ReactionEvent, type ReactionRequest, type ReadRequest, type RealtimeStatus, type TypingEvent, type Unsubscribe, UserChannel, type Uuid, version };
|
|
994
|
+
export { type AddMemberOptions, ApiError, type Attachment, type AttachmentDownloadResponse, AttachmentHandle, type AttachmentOptions, type AttachmentProgressEvent, type AttachmentStatus, type AttachmentUploadInput, type AttachmentUploadRequest, type AttachmentUploadResponse, AttachmentsResource, AuthError, type Conversation, ConversationChannel, type ConversationCreateRequest, type ConversationCreatedEvent, ConversationHandle, type ConversationList, ConversationMessages, type ConversationType, type ConversationUpdateRequest, ConversationsResource, type ErrorEnvelope, type IsoDateTime, type Me, MeResource, type MemberReadEvent, type MemberRole, type Membership, type MembershipCreateRequest, type MembershipList, type MentionEvent, type Message, type MessageCreateRequest, type MessageDeletedEvent, MessageHandle, type MessageList, type MessageNewEvent, type MessageType, type MessageUpdateRequest, type MessageUpdatedEvent, MessagesResource, NetworkError, POOLSE_API_URL, Poolse, type PoolseConfig, PoolseError, PoolseRealtime, type PoolseUserProfile, type PresenceSnapshot, type QuotedMessagePreview, RateLimitedError, type ReactionEvent, type ReactionRequest, type ReadRequest, type RealtimeStatus, type TypingEvent, type Unsubscribe, UserChannel, UsersResource, type Uuid, version };
|
package/dist/index.js
CHANGED
|
@@ -26,7 +26,8 @@ function resolveConfig(config) {
|
|
|
26
26
|
generateIdempotencyKey: config.generateIdempotencyKey ?? defaultIdempotencyKey,
|
|
27
27
|
wsUrl: config.wsUrl,
|
|
28
28
|
socketPath: config.socketPath ?? "/socket",
|
|
29
|
-
onSocketError: config.onSocketError
|
|
29
|
+
onSocketError: config.onSocketError,
|
|
30
|
+
userResolver: config.userResolver
|
|
30
31
|
};
|
|
31
32
|
}
|
|
32
33
|
function trimTrailingSlash(s) {
|
|
@@ -418,6 +419,14 @@ var AttachmentsResource = class {
|
|
|
418
419
|
...input.filename !== void 0 ? { original_filename: input.filename } : {}
|
|
419
420
|
};
|
|
420
421
|
const { attachment, upload } = await this.requestUpload(req, opts);
|
|
422
|
+
if (opts.onProgress && typeof XMLHttpRequest !== "undefined") {
|
|
423
|
+
await xhrPut(upload.url, upload.method.toUpperCase(), upload.headers, input.body, {
|
|
424
|
+
byteSize: input.byteSize,
|
|
425
|
+
onProgress: opts.onProgress,
|
|
426
|
+
...opts.signal ? { signal: opts.signal } : {}
|
|
427
|
+
});
|
|
428
|
+
return attachment;
|
|
429
|
+
}
|
|
421
430
|
const putInit = {
|
|
422
431
|
method: upload.method.toUpperCase(),
|
|
423
432
|
headers: upload.headers,
|
|
@@ -469,6 +478,39 @@ var AttachmentHandle = class {
|
|
|
469
478
|
});
|
|
470
479
|
}
|
|
471
480
|
};
|
|
481
|
+
function xhrPut(url, method, headers, body, opts) {
|
|
482
|
+
return new Promise((resolve, reject) => {
|
|
483
|
+
const xhr = new XMLHttpRequest();
|
|
484
|
+
xhr.open(method, url);
|
|
485
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
486
|
+
try {
|
|
487
|
+
xhr.setRequestHeader(k, v);
|
|
488
|
+
} catch {
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
xhr.upload.onprogress = (e) => {
|
|
492
|
+
const total = e.lengthComputable ? e.total : opts.byteSize;
|
|
493
|
+
opts.onProgress({ loaded: e.loaded, total });
|
|
494
|
+
};
|
|
495
|
+
xhr.onload = () => {
|
|
496
|
+
if (xhr.status >= 200 && xhr.status < 300) resolve();
|
|
497
|
+
else
|
|
498
|
+
reject(new Error(`Poolse: presigned upload PUT failed (${xhr.status} ${xhr.statusText})`));
|
|
499
|
+
};
|
|
500
|
+
xhr.onerror = () => reject(new Error("Poolse: presigned upload PUT failed (network error)"));
|
|
501
|
+
xhr.onabort = () => {
|
|
502
|
+
reject(new DOMException("Upload aborted", "AbortError"));
|
|
503
|
+
};
|
|
504
|
+
if (opts.signal) {
|
|
505
|
+
if (opts.signal.aborted) {
|
|
506
|
+
xhr.abort();
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
opts.signal.addEventListener("abort", () => xhr.abort(), { once: true });
|
|
510
|
+
}
|
|
511
|
+
xhr.send(body);
|
|
512
|
+
});
|
|
513
|
+
}
|
|
472
514
|
|
|
473
515
|
// src/resources/messages.ts
|
|
474
516
|
var ConversationMessages = class {
|
|
@@ -721,6 +763,100 @@ var MeResource = class {
|
|
|
721
763
|
}
|
|
722
764
|
};
|
|
723
765
|
|
|
766
|
+
// src/resources/users.ts
|
|
767
|
+
var UsersResource = class {
|
|
768
|
+
constructor(config) {
|
|
769
|
+
this.config = config;
|
|
770
|
+
}
|
|
771
|
+
config;
|
|
772
|
+
cache = /* @__PURE__ */ new Map();
|
|
773
|
+
pending = /* @__PURE__ */ new Map();
|
|
774
|
+
listeners = /* @__PURE__ */ new Map();
|
|
775
|
+
/**
|
|
776
|
+
* Get the cached value if present. Returns `undefined` to mean
|
|
777
|
+
* "not in cache yet" (different from `null`, which means "resolver
|
|
778
|
+
* ran and the user wasn't found").
|
|
779
|
+
*/
|
|
780
|
+
peek(userId) {
|
|
781
|
+
return this.cache.get(userId);
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Resolve a user, hitting the customer's `userResolver` on cache
|
|
785
|
+
* miss. Concurrent calls for the same id share one Promise.
|
|
786
|
+
*/
|
|
787
|
+
async get(userId) {
|
|
788
|
+
if (this.cache.has(userId)) {
|
|
789
|
+
return this.cache.get(userId) ?? null;
|
|
790
|
+
}
|
|
791
|
+
const existingPending = this.pending.get(userId);
|
|
792
|
+
if (existingPending) return existingPending;
|
|
793
|
+
const resolver = this.config.userResolver;
|
|
794
|
+
if (!resolver) {
|
|
795
|
+
this.cache.set(userId, null);
|
|
796
|
+
this.notify(userId);
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
799
|
+
const promise = Promise.resolve().then(() => resolver(userId)).then(
|
|
800
|
+
(profile) => {
|
|
801
|
+
this.cache.set(userId, profile ?? null);
|
|
802
|
+
this.pending.delete(userId);
|
|
803
|
+
this.notify(userId);
|
|
804
|
+
return profile ?? null;
|
|
805
|
+
},
|
|
806
|
+
(err) => {
|
|
807
|
+
console.error("[poolse] userResolver failed for", userId, err);
|
|
808
|
+
this.cache.set(userId, null);
|
|
809
|
+
this.pending.delete(userId);
|
|
810
|
+
this.notify(userId);
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
);
|
|
814
|
+
this.pending.set(userId, promise);
|
|
815
|
+
return promise;
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Subscribe to changes for a single user id. The listener fires
|
|
819
|
+
* when the resolver lands (or when the entry is invalidated).
|
|
820
|
+
* Returns an unsubscribe.
|
|
821
|
+
*
|
|
822
|
+
* `useUser` in @poolse/react uses this with `useSyncExternalStore`.
|
|
823
|
+
*/
|
|
824
|
+
subscribe(userId, listener) {
|
|
825
|
+
let set = this.listeners.get(userId);
|
|
826
|
+
if (!set) {
|
|
827
|
+
set = /* @__PURE__ */ new Set();
|
|
828
|
+
this.listeners.set(userId, set);
|
|
829
|
+
}
|
|
830
|
+
set.add(listener);
|
|
831
|
+
return () => {
|
|
832
|
+
set?.delete(listener);
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
/** Drop a single cached entry — next `get` re-fetches via the resolver. */
|
|
836
|
+
invalidate(userId) {
|
|
837
|
+
this.cache.delete(userId);
|
|
838
|
+
this.pending.delete(userId);
|
|
839
|
+
this.notify(userId);
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Drop the entire cache. Use after a sign-out, tenant swap, or any
|
|
843
|
+
* other event that invalidates every cached profile (e.g., the
|
|
844
|
+
* customer just renamed every user in bulk).
|
|
845
|
+
*/
|
|
846
|
+
invalidateAll() {
|
|
847
|
+
this.cache.clear();
|
|
848
|
+
this.pending.clear();
|
|
849
|
+
for (const userId of this.listeners.keys()) {
|
|
850
|
+
this.notify(userId);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
notify(userId) {
|
|
854
|
+
const set = this.listeners.get(userId);
|
|
855
|
+
if (!set) return;
|
|
856
|
+
for (const l of set) l();
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
|
|
724
860
|
// src/rest-client.ts
|
|
725
861
|
var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET"]);
|
|
726
862
|
var RestClient = class {
|
|
@@ -942,6 +1078,17 @@ var Poolse = class {
|
|
|
942
1078
|
messages;
|
|
943
1079
|
/** `/v1/attachments/*` — presigned-URL uploads/downloads. */
|
|
944
1080
|
attachments;
|
|
1081
|
+
/**
|
|
1082
|
+
* Customer-supplied user metadata, cached + dedup'd.
|
|
1083
|
+
* `chat.users.get(userId)` returns `{ displayName, avatarUrl }`
|
|
1084
|
+
* via the optional `config.userResolver`. UI components
|
|
1085
|
+
* (`MessageBubble`, `MemberList`, `TypingIndicator`) pick this up
|
|
1086
|
+
* automatically via the `useUser` hook in `@poolse/react`.
|
|
1087
|
+
*
|
|
1088
|
+
* If no resolver is configured, `get` always returns `null` and
|
|
1089
|
+
* UI falls back to the userId slice + initials avatar.
|
|
1090
|
+
*/
|
|
1091
|
+
users;
|
|
945
1092
|
/**
|
|
946
1093
|
* Low-level REST client. Exposed for advanced use cases (custom endpoints,
|
|
947
1094
|
* raw retry/headers control). Most callers should use the resources above.
|
|
@@ -969,6 +1116,7 @@ var Poolse = class {
|
|
|
969
1116
|
this.conversations = new ConversationsResource(this.rest);
|
|
970
1117
|
this.messages = new MessagesResource(this.rest);
|
|
971
1118
|
this.attachments = new AttachmentsResource(this.rest, cachedConfig.fetch);
|
|
1119
|
+
this.users = new UsersResource(cachedConfig);
|
|
972
1120
|
this.realtime = new PoolseRealtime(cachedConfig, this.tokenCache, {
|
|
973
1121
|
...this.resolved.wsUrl !== void 0 ? { wsUrl: this.resolved.wsUrl } : {},
|
|
974
1122
|
socketPath: this.resolved.socketPath
|
|
@@ -986,8 +1134,8 @@ var Poolse = class {
|
|
|
986
1134
|
};
|
|
987
1135
|
|
|
988
1136
|
// src/version.ts
|
|
989
|
-
var version = "0.0.
|
|
1137
|
+
var version = "1.0.0-rc.0";
|
|
990
1138
|
|
|
991
|
-
export { ApiError, AttachmentHandle, AttachmentsResource, AuthError, ConversationChannel, ConversationHandle, ConversationMessages, ConversationsResource, MeResource, MessageHandle, MessagesResource, NetworkError, POOLSE_API_URL, Poolse, PoolseError, PoolseRealtime, RateLimitedError, UserChannel, version };
|
|
1139
|
+
export { ApiError, AttachmentHandle, AttachmentsResource, AuthError, ConversationChannel, ConversationHandle, ConversationMessages, ConversationsResource, MeResource, MessageHandle, MessagesResource, NetworkError, POOLSE_API_URL, Poolse, PoolseError, PoolseRealtime, RateLimitedError, UserChannel, UsersResource, version };
|
|
992
1140
|
//# sourceMappingURL=index.js.map
|
|
993
1141
|
//# sourceMappingURL=index.js.map
|