@poolse/sdk 1.0.1-beta.0 → 1.0.2
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 +3 -1
- package/dist/index.cjs +79 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -2
- package/dist/index.d.ts +23 -2
- package/dist/index.js +79 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,12 +48,14 @@ chat.destroy();
|
|
|
48
48
|
|
|
49
49
|
## What it covers
|
|
50
50
|
|
|
51
|
-
- **REST resources**: `me`, `conversations` (incl. members), `messages` (send / edit / delete / replies / reactions / mark-read), `attachments` (presigned upload + download + delete).
|
|
51
|
+
- **REST resources**: `me`, `conversations` (incl. members), `messages` (send / edit / delete / replies / reactions / mark-read / quote-replies), `attachments` (presigned upload + download + delete with upload progress), `users` (customer-supplied profile cache).
|
|
52
52
|
- **Realtime channels**: `message:new`, `message:updated`, `message:deleted`, `typing:start/stop`, `reaction:added/removed`, presence state + diff, per-user `mention:new` + `conversation:created`.
|
|
53
53
|
- **Token caching** — `getToken` is called once per JWT lifetime, not per request. Auto-invalidates on 401 and re-issues.
|
|
54
54
|
- **Idempotency** — every non-GET request carries an auto-generated `Idempotency-Key`, so retries (network or 5xx) never insert duplicates.
|
|
55
55
|
- **Backoff** — exponential with full jitter, honors `Retry-After` on 429, never retries `AbortError`.
|
|
56
56
|
- **Typed errors** — `AuthError` / `ApiError` / `NetworkError` / `RateLimitedError`, all `instanceof`-checkable.
|
|
57
|
+
- **Upload progress** — pass `onProgress` to `attachments.upload(...)` and the SDK switches to XHR for the PUT so byte-level progress events fire during the upload to your storage backend.
|
|
58
|
+
- **User resolution** — pass `userResolver(userId)` and `chat.users.get(userId)` returns customer-supplied `{ displayName, avatarUrl }` (in-memory cached + dedup'd). The React UI components pick this up automatically; vanilla callers can read it directly.
|
|
57
59
|
|
|
58
60
|
## Architecture pattern
|
|
59
61
|
|
package/dist/index.cjs
CHANGED
|
@@ -232,9 +232,32 @@ var ConversationChannel = class {
|
|
|
232
232
|
// per event name (no matter how many JS listeners) and fan out
|
|
233
233
|
// ourselves — much cheaper than re-binding on every subscription.
|
|
234
234
|
listeners = /* @__PURE__ */ new Map();
|
|
235
|
+
// Phoenix.Presence pushes `presence_state` exactly ONCE right after
|
|
236
|
+
// join, then `presence_diff` for every change. Late subscribers
|
|
237
|
+
// (MemberList mounted after ConversationView has already joined the
|
|
238
|
+
// channel) would otherwise never see the initial snapshot and stay
|
|
239
|
+
// empty until somebody else joins or leaves. We cache the running
|
|
240
|
+
// state here and replay it on subscribe.
|
|
241
|
+
presenceState = {};
|
|
242
|
+
presenceStateSeen = false;
|
|
235
243
|
constructor(conversationId, channel) {
|
|
236
244
|
this.conversationId = conversationId;
|
|
237
245
|
this.channel = channel;
|
|
246
|
+
channel.on("presence_state", (payload) => {
|
|
247
|
+
this.presenceState = payload ?? {};
|
|
248
|
+
this.presenceStateSeen = true;
|
|
249
|
+
const listeners = this.listeners.get("presence_state");
|
|
250
|
+
if (listeners) listeners.forEach((l) => l(this.presenceState));
|
|
251
|
+
});
|
|
252
|
+
channel.on("presence_diff", (payload) => {
|
|
253
|
+
const diff = payload ?? {};
|
|
254
|
+
const next = { ...this.presenceState };
|
|
255
|
+
if (diff.joins) for (const [k, v] of Object.entries(diff.joins)) next[k] = v;
|
|
256
|
+
if (diff.leaves) for (const k of Object.keys(diff.leaves)) delete next[k];
|
|
257
|
+
this.presenceState = next;
|
|
258
|
+
const listeners = this.listeners.get("presence_diff");
|
|
259
|
+
if (listeners) listeners.forEach((l) => l(payload));
|
|
260
|
+
});
|
|
238
261
|
}
|
|
239
262
|
/** New message pushed to the conversation. */
|
|
240
263
|
onMessage(fn) {
|
|
@@ -268,11 +291,24 @@ var ConversationChannel = class {
|
|
|
268
291
|
return this.subscribe("member:read", fn);
|
|
269
292
|
}
|
|
270
293
|
onPresenceState(fn) {
|
|
271
|
-
|
|
294
|
+
let set = this.listeners.get("presence_state");
|
|
295
|
+
if (!set) {
|
|
296
|
+
set = /* @__PURE__ */ new Set();
|
|
297
|
+
this.listeners.set("presence_state", set);
|
|
298
|
+
}
|
|
299
|
+
set.add(fn);
|
|
300
|
+
if (this.presenceStateSeen) fn(this.presenceState);
|
|
301
|
+
return () => {
|
|
302
|
+
set.delete(fn);
|
|
303
|
+
};
|
|
272
304
|
}
|
|
273
305
|
onPresenceDiff(fn) {
|
|
274
306
|
return this.subscribe("presence_diff", fn);
|
|
275
307
|
}
|
|
308
|
+
/** Current presence snapshot for sync access — usually used in tests. */
|
|
309
|
+
getPresenceState() {
|
|
310
|
+
return this.presenceState;
|
|
311
|
+
}
|
|
276
312
|
/** Send a typing ping to the server. Debounced server-side. */
|
|
277
313
|
sendTyping() {
|
|
278
314
|
this.channel.push("typing", {});
|
|
@@ -421,6 +457,14 @@ var AttachmentsResource = class {
|
|
|
421
457
|
...input.filename !== void 0 ? { original_filename: input.filename } : {}
|
|
422
458
|
};
|
|
423
459
|
const { attachment, upload } = await this.requestUpload(req, opts);
|
|
460
|
+
if (opts.onProgress && typeof XMLHttpRequest !== "undefined") {
|
|
461
|
+
await xhrPut(upload.url, upload.method.toUpperCase(), upload.headers, input.body, {
|
|
462
|
+
byteSize: input.byteSize,
|
|
463
|
+
onProgress: opts.onProgress,
|
|
464
|
+
...opts.signal ? { signal: opts.signal } : {}
|
|
465
|
+
});
|
|
466
|
+
return attachment;
|
|
467
|
+
}
|
|
424
468
|
const putInit = {
|
|
425
469
|
method: upload.method.toUpperCase(),
|
|
426
470
|
headers: upload.headers,
|
|
@@ -472,6 +516,39 @@ var AttachmentHandle = class {
|
|
|
472
516
|
});
|
|
473
517
|
}
|
|
474
518
|
};
|
|
519
|
+
function xhrPut(url, method, headers, body, opts) {
|
|
520
|
+
return new Promise((resolve, reject) => {
|
|
521
|
+
const xhr = new XMLHttpRequest();
|
|
522
|
+
xhr.open(method, url);
|
|
523
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
524
|
+
try {
|
|
525
|
+
xhr.setRequestHeader(k, v);
|
|
526
|
+
} catch {
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
xhr.upload.onprogress = (e) => {
|
|
530
|
+
const total = e.lengthComputable ? e.total : opts.byteSize;
|
|
531
|
+
opts.onProgress({ loaded: e.loaded, total });
|
|
532
|
+
};
|
|
533
|
+
xhr.onload = () => {
|
|
534
|
+
if (xhr.status >= 200 && xhr.status < 300) resolve();
|
|
535
|
+
else
|
|
536
|
+
reject(new Error(`Poolse: presigned upload PUT failed (${xhr.status} ${xhr.statusText})`));
|
|
537
|
+
};
|
|
538
|
+
xhr.onerror = () => reject(new Error("Poolse: presigned upload PUT failed (network error)"));
|
|
539
|
+
xhr.onabort = () => {
|
|
540
|
+
reject(new DOMException("Upload aborted", "AbortError"));
|
|
541
|
+
};
|
|
542
|
+
if (opts.signal) {
|
|
543
|
+
if (opts.signal.aborted) {
|
|
544
|
+
xhr.abort();
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
opts.signal.addEventListener("abort", () => xhr.abort(), { once: true });
|
|
548
|
+
}
|
|
549
|
+
xhr.send(body);
|
|
550
|
+
});
|
|
551
|
+
}
|
|
475
552
|
|
|
476
553
|
// src/resources/messages.ts
|
|
477
554
|
var ConversationMessages = class {
|
|
@@ -1095,7 +1172,7 @@ var Poolse = class {
|
|
|
1095
1172
|
};
|
|
1096
1173
|
|
|
1097
1174
|
// src/version.ts
|
|
1098
|
-
var version = "
|
|
1175
|
+
var version = "1.0.2";
|
|
1099
1176
|
|
|
1100
1177
|
exports.ApiError = ApiError;
|
|
1101
1178
|
exports.AttachmentHandle = AttachmentHandle;
|