Socket
Socket
Sign inDemoInstall

@ably/chat

Package Overview
Dependencies
Maintainers
0
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ably/chat - npm Package Compare versions

Comparing version 0.1.0 to 0.2.0

dist/chat/chat-api.d.ts

7

dist/chat/discontinuity.d.ts

@@ -1,2 +0,2 @@

import { default as EventEmitter } from './utils/EventEmitter.js';
import { default as EventEmitter } from './utils/event-emitter.js';
import * as Ably from 'ably';

@@ -8,5 +8,6 @@ /**

/**
* The channel that this object is associated with.
* A promise of the channel that this object is associated with. The promise
* is resolved when the feature has finished initializing.
*/
get channel(): Ably.RealtimeChannel;
get channel(): Promise<Ably.RealtimeChannel>;
/**

@@ -13,0 +14,0 @@ * Called when a discontinuity is detected on the channel.

/**
* @module chat-js
*/
export { ChatClient } from './Chat.js';
export { ChatClient } from './chat.js';
export type { ClientOptions } from './config.js';
export type { Connection } from './Connection.js';
export type { ConnectionLifecycle, ConnectionStatus, ConnectionStatusChange, ConnectionStatusListener, OnConnectionStatusChangeResponse, } from './ConnectionStatus.js';
export type { Connection } from './connection.js';
export type { ConnectionStatus, ConnectionStatusChange, ConnectionStatusListener, OnConnectionStatusChangeResponse, } from './connection-status.js';
export { ConnectionLifecycle } from './connection-status.js';
export type { DiscontinuityListener, OnDiscontinuitySubscriptionResponse } from './discontinuity.js';
export type { ErrorCodes } from './errors.js';
export { MessageEvents, PresenceEvents } from './events.js';
export type { Headers } from './Headers.js';
export type { LogContext, LogHandler } from './logger.js';
export type { Headers } from './headers.js';
export { ChatEntityType, chatMessageFromAblyMessage, chatMessageFromEncoded, getEntityTypeFromAblyMessage, getEntityTypeFromEncoded, reactionFromAblyMessage, reactionFromEncoded, } from './helpers.js';
export type { LogContext, Logger, LogHandler } from './logger.js';
export { LogLevel } from './logger.js';
export type { Message, MessageHeaders, MessageMetadata } from './Message.js';
export type { MessageEventPayload, MessageListener, Messages, MessageSubscriptionResponse, QueryOptions, SendMessageParams, } from './Messages.js';
export type { Metadata } from './Metadata.js';
export type { Occupancy, OccupancyEvent, OccupancyListener, OccupancySubscriptionResponse } from './Occupancy.js';
export type { Presence, PresenceData, PresenceEvent, PresenceListener, PresenceMember, PresenceSubscriptionResponse, } from './Presence.js';
export type { Message, MessageHeaders, MessageMetadata } from './message.js';
export type { MessageEventPayload, MessageListener, Messages, MessageSubscriptionResponse, QueryOptions, SendMessageParams, } from './messages.js';
export type { Metadata } from './metadata.js';
export type { Occupancy, OccupancyEvent, OccupancyListener, OccupancySubscriptionResponse } from './occupancy.js';
export type { Presence, PresenceData, PresenceEvent, PresenceListener, PresenceMember, PresenceSubscriptionResponse, } from './presence.js';
export type { PaginatedResult } from './query.js';
export type { Reaction } from './Reaction.js';
export type { Room } from './Room.js';
export type { OccupancyOptions, PresenceOptions, RoomOptions, RoomReactionsOptions, TypingOptions, } from './RoomOptions.js';
export { RoomOptionsDefaults } from './RoomOptions.js';
export type { RoomReactionListener, RoomReactions, RoomReactionsSubscriptionResponse, SendReactionParams, } from './RoomReactions.js';
export type { Rooms } from './Rooms.js';
export type { OnRoomStatusChangeResponse, RoomLifecycle, RoomStatus, RoomStatusChange, RoomStatusListener, } from './RoomStatus.js';
export type { Typing, TypingEvent, TypingListener, TypingSubscriptionResponse } from './Typing.js';
export type { Reaction } from './reaction.js';
export type { Room } from './room.js';
export type { OccupancyOptions, PresenceOptions, RoomOptions, RoomReactionsOptions, TypingOptions, } from './room-options.js';
export { RoomOptionsDefaults } from './room-options.js';
export type { RoomReactionListener, RoomReactions, RoomReactionsSubscriptionResponse, SendReactionParams, } from './room-reactions.js';
export type { OnRoomStatusChangeResponse, RoomStatus, RoomStatusChange, RoomStatusListener } from './room-status.js';
export { RoomLifecycle } from './room-status.js';
export type { Rooms } from './rooms.js';
export type { Typing, TypingEvent, TypingListener, TypingSubscriptionResponse } from './typing.js';
export type { ChannelStateChange, ErrorInfo, RealtimePresenceParams } from 'ably';

@@ -1,3 +0,3 @@

export declare const VERSION = "0.1.0";
export declare const CHANNEL_OPTIONS_AGENT_STRING = "chat-js/0.1.0";
export declare const VERSION = "0.2.0";
export declare const CHANNEL_OPTIONS_AGENT_STRING = "chat-js/0.2.0";
export declare const DEFAULT_CHANNEL_OPTIONS: {

@@ -4,0 +4,0 @@ params: {

@@ -1,13 +0,439 @@

import { useState as n } from "react";
const s = () => {
const [e, t] = n(0);
return { count: e, increment: () => {
t(e + 1);
}, decrement: () => {
t(e - 1);
} };
import * as j from "react";
import W, { createContext as q, useRef as E, useEffect as g, useCallback as a, useState as C, useMemo as Q, useContext as Z } from "react";
import * as F from "ably";
import { jsx as z } from "react/jsx-runtime";
import { ConnectionLifecycle as X, RoomLifecycle as Y } from "@ably/chat";
const J = q(void 0), k = Symbol.for("__ABLY_CHAT_CLIENT_CONTEXT__"), B = typeof globalThis > "u" ? {} : globalThis;
function $() {
let t = B[k];
return t || (t = B[k] = W.createContext({})), t;
}
const O = $(), K = "default", se = ({ children: t, client: n }) => {
const u = j.useContext(O), e = j.useMemo(() => (n.addReactAgent(), { ...u, [K]: { client: n } }), [n, u]);
return /* @__PURE__ */ z(O.Provider, { value: e, children: t });
}, V = () => {
const t = W.useContext(O)[K];
if (!t)
throw new F.ErrorInfo("useChatClient hook must be used within a chat client provider", 4e4, 400);
return t.client;
}, h = (t) => {
const n = E(t);
g(() => {
n.current = t;
});
const u = a((...e) => {
n.current && n.current(...e);
}, []);
return t ? u : void 0;
}, L = (t) => {
const n = V();
n.logger.trace("useChatConnection();", t);
const [u, e] = C(n.connection.status.current), [m, r] = C(n.connection.status.error), [o, d] = C(n.connection);
g(() => {
d(n.connection), r(n.connection.status.error), e(n.connection.status.current);
}, [n]);
const i = h(t == null ? void 0 : t.onStatusChange);
return g(() => {
n.logger.debug("useChatConnection(); applying internal listener");
const { off: c } = n.connection.status.onChange((s) => {
e(s.current), r(s.error);
});
return () => {
n.logger.debug("useChatConnection(); cleaning up listener"), c();
};
}, [n.connection.status, n.logger]), g(() => {
if (!i) return;
n.logger.debug("useChatConnection(); applying client listener");
const { off: c } = n.connection.status.onChange(i);
return () => {
n.logger.debug("useChatConnection(); cleaning up client listener"), c();
};
}, [n.connection.status, n.logger, i]), {
currentStatus: u,
error: m,
connection: o
};
}, D = () => {
const t = V();
return Q(() => t.logger, [t]);
};
function N(t) {
return {
status: t.current,
error: t.error
};
}
const M = (t) => {
const n = Z(J), u = D();
if (u.trace("useRoom();", { roomId: n == null ? void 0 : n.room.roomId }), !n)
throw u.error("useRoom(); must be used within a ChatRoomProvider"), new F.ErrorInfo("useRoom hook must be used within a ChatRoomProvider", 4e4, 400);
const { currentStatus: e, error: m } = L({
onStatusChange: t == null ? void 0 : t.onConnectionStatusChange
}), r = n.room, [o, d] = C(N(r.status)), i = h(t == null ? void 0 : t.onStatusChange);
g(() => {
u.debug("useRoom(); setting up room status listener", { roomId: r.roomId });
const { off: l } = r.status.onChange((f) => {
d(N(f));
});
return d((f) => r.status.current !== f.status || r.status.error !== f.error ? N(r.status) : f), () => {
u.debug("useRoom(); removing room status listener", { roomId: r.roomId }), l();
};
}, [r, u]), g(() => {
if (!i) return;
u.debug("useRoom(); applying user-provided listener", { roomId: r.roomId });
const { off: l } = r.status.onChange(i);
return () => {
u.debug("useRoom(); removing user-provided listener", { roomId: r.roomId }), l();
};
}, [r, i, u]);
const c = a(() => r.attach(), [r]), s = a(() => r.detach(), [r]);
return {
room: r,
attach: c,
detach: s,
roomStatus: o.status,
roomError: o.error,
connectionStatus: e,
connectionError: m
};
}, ce = (t) => {
const { currentStatus: n, error: u } = L({
onStatusChange: t == null ? void 0 : t.onConnectionStatusChange
}), { room: e, roomError: m, roomStatus: r } = M({
onStatusChange: t == null ? void 0 : t.onRoomStatusChange
}), o = D();
o.trace("useMessages();", { params: t, roomId: e.roomId });
const d = h(t == null ? void 0 : t.listener), i = h(t == null ? void 0 : t.onDiscontinuity), c = a((b) => e.messages.send(b), [e]), s = a((b) => e.messages.get(b), [e]), [l, f] = C();
return g(() => {
if (!d) return;
o.debug("useMessages(); applying listener", { roomId: e.roomId });
let b = !1;
const S = e.messages.subscribe(d);
return f(() => (o.debug("useMessages(); setting getPreviousMessages state", { roomId: e.roomId }), (y) => b ? (o.debug("useMessages(); getPreviousMessages called after unmount", { roomId: e.roomId }), Promise.resolve({
items: [],
hasNext: () => !1,
isLast: () => !0,
next: () => {
},
previous: () => {
},
current: () => {
},
first: () => {
}
})) : S.getPreviousMessages(y))), () => {
o.debug("useMessages(); removing listener and getPreviousMessages state", { roomId: e.roomId }), b = !0, S.unsubscribe(), f(void 0);
};
}, [e, o, d]), g(() => {
if (!i) return;
o.debug("useMessages(); applying onDiscontinuity listener", { roomId: e.roomId });
const { off: b } = e.messages.onDiscontinuity(i);
return () => {
o.debug("useMessages(); removing onDiscontinuity listener", { roomId: e.roomId }), b();
};
}, [e, o, i]), {
send: c,
get: s,
messages: e.messages,
getPreviousMessages: l,
connectionStatus: n,
connectionError: u,
roomStatus: r,
roomError: m
};
}, ue = (t) => {
const { currentStatus: n, error: u } = L({
onStatusChange: t == null ? void 0 : t.onConnectionStatusChange
}), { room: e, roomError: m, roomStatus: r } = M({
onStatusChange: t == null ? void 0 : t.onRoomStatusChange
}), o = D();
o.trace("useOccupancy();", { params: t, roomId: e.roomId });
const [d, i] = C({
connections: 0,
presenceMembers: 0
}), c = h(t == null ? void 0 : t.listener), s = h(t == null ? void 0 : t.onDiscontinuity);
return g(() => {
if (!s) return;
o.debug("useOccupancy(); applying onDiscontinuity listener", { roomId: e.roomId });
const { off: l } = e.occupancy.onDiscontinuity(s);
return () => {
o.debug("useOccupancy(); removing onDiscontinuity listener", { roomId: e.roomId }), l();
};
}, [e, s, o]), g(() => {
o.debug("useOccupancy(); applying internal listener", { roomId: e.roomId });
const { unsubscribe: l } = e.occupancy.subscribe((f) => {
i({
connections: f.connections,
presenceMembers: f.presenceMembers
});
});
return () => {
o.debug("useOccupancy(); cleaning up internal listener", { roomId: e.roomId }), l();
};
}, [e, o]), g(() => {
if (!c) return;
o.debug("useOccupancy(); applying listener", { roomId: e.roomId });
const { unsubscribe: l } = e.occupancy.subscribe(c);
return () => {
o.debug("useOccupancy(); cleaning up listener", { roomId: e.roomId }), l();
};
}, [c, e, o]), {
connectionStatus: n,
connectionError: u,
roomStatus: r,
roomError: m,
occupancy: e.occupancy,
connections: d.connections,
presenceMembers: d.presenceMembers
};
}, H = /* @__PURE__ */ new Set([
X.Suspended,
X.Failed
]), ie = (t) => {
const { currentStatus: n, error: u } = L({
onStatusChange: t == null ? void 0 : t.onConnectionStatusChange
}), { room: e, roomError: m, roomStatus: r } = M({
onStatusChange: t == null ? void 0 : t.onRoomStatusChange
}), o = D();
o.trace("usePresence();", { params: t, roomId: e.roomId });
const [d, i] = C(!1), [c, s] = C(), l = E({ roomStatus: r, connectionStatus: n }), f = h(t == null ? void 0 : t.onDiscontinuity), b = E(t);
g(() => {
b.current = t;
}, [t]), g(() => {
l.current = { roomStatus: r, connectionStatus: n };
}, [r, n]), g(() => {
var P;
const y = r === Y.Attached && !H.has(n), T = l.current.roomStatus === Y.Attached && !H.has(l.current.connectionStatus);
if (y)
return e.presence.enter((P = b.current) == null ? void 0 : P.enterWithData).then(() => {
o.debug("usePresence(); entered room", { roomId: e.roomId }), i(!0), s(void 0);
}).catch((I) => {
o.error("usePresence(); error entering room", { error: I, roomId: e.roomId }), s(I);
}), () => {
var I;
T && e.presence.leave((I = b.current) == null ? void 0 : I.leaveWithData).then(() => {
o.debug("usePresence(); left room", { roomId: e.roomId }), i(!1), s(void 0);
}).catch((_) => {
o.error("usePresence(); error leaving room", { error: _, roomId: e.roomId }), s(_);
});
};
}, [e, n, r, o]), g(() => {
if (!f) return;
o.debug("usePresence(); applying onDiscontinuity listener", { roomId: e.roomId });
const { off: y } = e.presence.onDiscontinuity(f);
return () => {
o.debug("usePresence(); removing onDiscontinuity listener", { roomId: e.roomId }), y();
};
}, [e, f, o]);
const S = a(
(y) => e.presence.update(y).then(() => {
i(!0), s(void 0);
}),
[e]
);
return {
connectionStatus: n,
connectionError: u,
roomStatus: r,
roomError: m,
update: S,
isPresent: d,
error: c,
presence: e.presence
};
}, p = 1500, ee = 3e4, te = 5, de = (t) => {
const { currentStatus: n, error: u } = L({
onStatusChange: t == null ? void 0 : t.onConnectionStatusChange
}), { room: e, roomError: m, roomStatus: r } = M({
onStatusChange: t == null ? void 0 : t.onRoomStatusChange
}), o = D();
o.trace("usePresenceListener();", { roomId: e.roomId });
const d = E(0), i = E(0), c = E(), s = E(0), l = E([]), [f, b] = C([]), S = E(), [y, T] = C(), P = h(t == null ? void 0 : t.listener), I = h(t == null ? void 0 : t.onDiscontinuity), _ = a(
(v) => {
o.debug("usePresenceListener(); setting error state", { error: v, roomId: e.roomId }), S.current = v, T(v);
},
[o, e.roomId]
), w = a(() => {
o.debug("usePresenceListener(); clearing error state", { roomId: e.roomId }), S.current = void 0, T(void 0);
}, [o, e.roomId]);
return g(() => {
const v = () => {
d.current += 1, c.current && (clearTimeout(c.current), c.current = void 0, s.current = 0), G(d.current);
}, G = (R) => {
e.presence.get({ waitForSync: !0 }).then((x) => {
o.debug("usePresenceListener(); fetched presence data", { presenceMembers: x, roomId: e.roomId }), c.current && (clearTimeout(c.current), c.current = void 0, s.current = 0), !(i.current >= R) && (i.current = R, l.current = x, b(x), S.current && w());
}).catch(() => {
if (!(s.current < te)) {
o.error("usePresenceListener(); failed to fetch presence data after max retries", {
roomId: e.roomId
}), _(new F.ErrorInfo("failed to fetch presence data after max retries", 5e4, 500));
return;
}
if (c.current) {
o.debug("usePresenceListener(); waiting for retry but new event received", { roomId: e.roomId });
return;
}
const U = Math.min(
ee,
p * Math.pow(2, s.current)
);
s.current += 1, o.debug("usePresenceListener(); retrying to fetch presence data", {
numRetries: s.current,
roomId: e.roomId
}), c.current = setTimeout(() => {
c.current = void 0, d.current += 1, G(d.current);
}, U);
});
};
let A;
return e.presence.get({ waitForSync: !0 }).then((R) => {
o.debug("usePresenceListener(); fetched initial presence data", { presenceMembers: R, roomId: e.roomId }), l.current = R, b(R), w();
}).catch((R) => {
o.error("usePresenceListener(); error fetching initial presence data", { error: R, roomId: e.roomId }), _(R);
}).finally(() => {
o.debug("usePresenceListener(); subscribing internal listener to presence events", {
roomId: e.roomId
}), A = e.presence.subscribe(() => {
v();
}).unsubscribe;
}), () => {
A && (o.debug("usePresenceListener(); cleaning up internal listener", { roomId: e.roomId }), A());
};
}, [e, _, w, o]), g(() => {
if (!P) return;
const { unsubscribe: v } = e.presence.subscribe(P);
return o.debug("usePresenceListener(); applying external listener", { roomId: e.roomId }), () => {
o.debug("usePresenceListener(); cleaning up external listener", { roomId: e.roomId }), v();
};
}, [e, P, o]), g(() => {
if (!I) return;
o.debug("usePresenceListener(); applying onDiscontinuity listener", { roomId: e.roomId });
const { off: v } = e.presence.onDiscontinuity(I);
return () => {
o.debug("usePresenceListener(); removing onDiscontinuity listener", { roomId: e.roomId }), v();
};
}, [e, I, o]), {
connectionStatus: n,
connectionError: u,
roomStatus: r,
roomError: m,
error: y,
presenceData: f,
presence: e.presence
};
}, ge = (t) => {
const { currentStatus: n, error: u } = L({
onStatusChange: t == null ? void 0 : t.onConnectionStatusChange
}), { room: e, roomError: m, roomStatus: r } = M({
onStatusChange: t == null ? void 0 : t.onRoomStatusChange
}), o = D();
o.trace("useRoomReactions();", { params: t, roomId: e.roomId });
const d = h(t == null ? void 0 : t.listener), i = h(t == null ? void 0 : t.onDiscontinuity);
g(() => {
if (!i) return;
o.debug("useRoomReactions(); applying onDiscontinuity listener", { roomId: e.roomId });
const { off: s } = e.reactions.onDiscontinuity(i);
return () => {
o.debug("useRoomReactions(); removing onDiscontinuity listener", { roomId: e.roomId }), s();
};
}, [e, i, o]), g(() => {
if (!d) return;
o.debug("useRoomReactions(); applying listener", { roomId: e.roomId });
const { unsubscribe: s } = e.reactions.subscribe(d);
return () => {
o.debug("useRoomReactions(); removing listener", { roomId: e.roomId }), s();
};
}, [e, d, o]);
const c = a((s) => e.reactions.send(s), [e.reactions]);
return {
connectionStatus: n,
connectionError: u,
roomStatus: r,
roomError: m,
send: c,
reactions: e.reactions
};
}, le = (t) => {
const { currentStatus: n, error: u } = L({
onStatusChange: t == null ? void 0 : t.onConnectionStatusChange
}), { room: e, roomError: m, roomStatus: r } = M({
onStatusChange: t == null ? void 0 : t.onRoomStatusChange
}), o = D();
o.trace("useTyping();", { roomId: e.roomId });
const [d, i] = C(/* @__PURE__ */ new Set()), [c, s] = C(), l = h(t == null ? void 0 : t.listener), f = h(t == null ? void 0 : t.onDiscontinuity);
g(() => {
s(void 0), i((I) => I.size === 0 ? I : /* @__PURE__ */ new Set());
let y = !0;
const T = (I) => {
I === void 0 ? o.debug("useTyping(); clearing error state", { roomId: e.roomId }) : o.error("useTyping(); setting error state", { error: I, roomId: e.roomId }), s(I);
};
e.typing.get().then((I) => {
y && i(I);
}).catch((I) => {
y && T(I);
});
const P = e.typing.subscribe((I) => {
T(void 0), i(I.currentlyTyping);
});
return () => {
o.debug("useTyping(); unsubscribing from typing events", { roomId: e.roomId }), y = !1, P.unsubscribe();
};
}, [e, o]), g(() => {
if (!f) return;
o.debug("useTyping(); applying onDiscontinuity listener", { roomId: e.roomId });
const { off: y } = e.typing.onDiscontinuity(f);
return () => {
o.debug("useTyping(); removing onDiscontinuity listener", { roomId: e.roomId }), y();
};
}, [e, f, o]), g(() => {
if (!l) return;
o.debug("useTyping(); applying listener", { roomId: e.roomId });
const { unsubscribe: y } = e.typing.subscribe(l);
return () => {
o.debug("useTyping(); removing listener", { roomId: e.roomId }), y();
};
}, [e, l, o]);
const b = a(() => e.typing.start(), [e.typing]), S = a(() => e.typing.stop(), [e.typing]);
return {
connectionStatus: n,
connectionError: u,
roomStatus: r,
roomError: m,
error: c,
start: b,
stop: S,
currentlyTyping: d,
typingIndicators: e.typing
};
}, Ie = ({
id: t,
options: n,
release: u = !0,
attach: e = !0,
children: m
}) => {
const r = V(), o = D();
o.trace("ChatRoomProvider();", { roomId: t, options: n, release: u, attach: e });
const [d, i] = C({ room: r.rooms.get(t, n) });
return g(() => {
const c = r.rooms.get(t, n);
return i((s) => s.room === c ? s : { room: c }), e && (o.debug("ChatRoomProvider(); attaching room", { roomId: t }), c.attach()), () => {
u ? (o.debug("ChatRoomProvider(); releasing room", { roomId: t }), r.rooms.release(t)) : e && (o.debug("ChatRoomProvider(); detaching room", { roomId: t }), c.detach());
};
}, [r, t, n, u, e, o]), /* @__PURE__ */ z(J.Provider, { value: d, children: m });
};
export {
s as useCounter
se as ChatClientProvider,
J as ChatRoomContext,
Ie as ChatRoomProvider,
V as useChatClient,
L as useChatConnection,
ce as useMessages,
ue as useOccupancy,
ie as usePresence,
de as usePresenceListener,
M as useRoom,
ge as useRoomReactions,
le as useTyping
};
//# sourceMappingURL=ably-chat-react.js.map
/**
* @module chat-react
*/
export { type TestHookReturnType, useCounter } from './hooks/useCounter.js';
export { type ChatClientContextProviderProps } from './contexts/chat-client-context.js';
export { ChatRoomContext, type ChatRoomContextType } from './contexts/chat-room-context.js';
export { useChatClient } from './hooks/use-chat-client.js';
export { useChatConnection, type UseChatConnectionOptions, type UseChatConnectionResponse, } from './hooks/use-chat-connection.js';
export { useMessages, type UseMessagesParams, type UseMessagesResponse } from './hooks/use-messages.js';
export { useOccupancy, type UseOccupancyParams, type UseOccupancyResponse } from './hooks/use-occupancy.js';
export { usePresence, type UsePresenceParams, type UsePresenceResponse } from './hooks/use-presence.js';
export { usePresenceListener, type UsePresenceListenerParams, type UsePresenceListenerResponse, } from './hooks/use-presence-listener.js';
export { useRoom, type UseRoomParams, type UseRoomResponse } from './hooks/use-room.js';
export { useRoomReactions, type UseRoomReactionsParams, type UseRoomReactionsResponse, } from './hooks/use-room-reactions.js';
export { type TypingParams, useTyping, type UseTypingResponse } from './hooks/use-typing.js';
export { ChatClientProvider, type ChatClientProviderProps } from './providers/chat-client-provider.js';
export { ChatRoomProvider, type ChatRoomProviderProps } from './providers/chat-room-provider.js';
export { type ChatStatusResponse } from './types/chat-status-response.js';
export { type StatusParams } from './types/status-params.js';
{
"name": "@ably/chat",
"version": "0.1.0",
"version": "0.2.0",
"description": "Ably Chat is a set of purpose-built APIs for a host of chat features enabling you to create 1:1, 1:Many, Many:1 and Many:Many chat rooms for any scale. It is designed to meet a wide range of chat use cases, such as livestreams, in-game communication, customer support, or social interactions in SaaS products.",

@@ -9,2 +9,3 @@ "type": "module",

"types": "dist/chat/index.d.ts",
"react-native": "dist/react/ably-chat-react.umd.cjs",
"exports": {

@@ -14,3 +15,4 @@ ".": {

"import": "./dist/chat/ably-chat.js",
"require": "./dist/chat/ably-chat.umd.cjs"
"require": "./dist/chat/ably-chat.umd.cjs",
"react-native": "./dist/chat/ably-chat.umd.cjs"
},

@@ -20,7 +22,9 @@ "./react": {

"import": "./dist/react/ably-chat-react.js",
"require": "./dist/react/ably-chat-react.umd.cjs"
}
"require": "./dist/react/ably-chat-react.umd.cjs",
"react-native": "./dist/react/ably-chat-react.umd.cjs"
},
"./package.json": "./package.json"
},
"scripts": {
"lint": "eslint . && cspell '{src,test}/**' './*.md'",
"lint": "eslint . && npm run cspell",
"lint:fix": "eslint --fix .",

@@ -34,9 +38,10 @@ "format": "prettier --write src test __mocks__ demo/src",

"build": "npm run build:chat && npm run build:react",
"build:chat": "vite build",
"build:chat": "vite build --config ./src/core/vite.config.ts --emptyOutDir",
"build:react": "vite build --config ./src/react/vite.config.ts --emptyOutDir",
"prepare": "npm run build",
"test:typescript": "tsc --project tsconfig.test.json",
"test:typescript": "tsc",
"demo:reload": "npm run build && cd demo && npm i file:../",
"docs": "typedoc",
"modulereport": "tsc --noEmit --esModuleInterop scripts/moduleReport.ts && esr scripts/moduleReport.ts"
"modulereport": "tsc --noEmit --esModuleInterop scripts/moduleReport.ts && esr scripts/moduleReport.ts",
"cspell": "cspell '{src,test}/**' './*.md'"
},

@@ -64,3 +69,3 @@ "files": [

"peerDependencies": {
"ably": "^2.2.1"
"ably": "^2.3.1"
},

@@ -78,2 +83,3 @@ "devDependencies": {

"eslint": "^8.57.0",
"eslint-plugin-compat": "^5.0.0",
"eslint-plugin-import": "^2.29.1",

@@ -86,2 +92,3 @@ "eslint-plugin-jsdoc": "^48.5.0",

"eslint-plugin-simple-import-sort": "^12.1.0",
"eslint-plugin-unicorn": "^54.0.0",
"jsdom": "^24.1.0",

@@ -107,3 +114,7 @@ "jsonwebtoken": "^9.0.2",

"@rollup/rollup-linux-x64-gnu": "^4.18"
}
},
"browserslist": [
"defaults",
"not op_mini all"
]
}

@@ -1,2 +0,2 @@

# Ably Chat SDK
# Ably Chat SDK for TypeScript and React

@@ -6,2 +6,3 @@ <p style="text-align: left">

<img src="https://badgen.net/github/license/3scale/saas-operator" alt="License" />
<img src="https://img.shields.io/npm/v/@ably/chat.svg?style=flat">
</p>

@@ -12,3 +13,3 @@

> [!IMPORTANT]
> This SDK is currently under development. If you are interested in being an early adopter and providing feedback then you can [sign up to the private beta](https://forms.gle/vB2kXhCXrTQpzHLu5) and are welcome to [provide us with feedback](https://forms.gle/mBw9M53NYuCBLFpMA).
> This SDK is currently under development. If you are interested in being an early adopter and providing feedback then you can [sign up to the private beta](https://forms.gle/vB2kXhCXrTQpzHLu5) and are welcome to [provide us with feedback](https://forms.gle/mBw9M53NYuCBLFpMA). Coming soon: chat moderation, editing and deleting messages.

@@ -19,2 +20,31 @@ Get started using the [📚 documentation](https://ably.com/docs/products/chat) and [🚀check out the live demo](https://ably-livestream-chat-demo.vercel.app/), or [📘 browse the API reference](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/).

## Supported Platforms
This SDK supports the following platforms:
**Browsers**: All major desktop and mobile browsers, including (but not limited to) Chrome, Firefox, Edge, Safari on iOS and macOS, Opera, and Android browsers. Internet Explorer is not supported.
**Node.js**: Version 20.x or newer.
**Typescript**: This library is written in TypeScript and has full TypeScript support.
**React**: The library ships with a number of providers and hooks for React, which provide a closer integration with that ecosystem. For more information on using Ably Chat in React, see the [React readme](./src/react/README.md).
**React Native** We aim to support all platforms supported by React Native. If you find any issues please raise an issue or contact us.
There is a known caveat in the current version of the library in environments where `structuredClone` is not defined. To address this, you can use a polyfill such as [@ungap/structured-clone](https://www.npmjs.com/package/@ungap/structured-clone).
## Supported chat features
This project is under development so we will be incrementally adding new features. At this stage, you'll find APIs for the following chat features:
- Chat rooms for 1:1, 1:many, many:1 and many:many participation.
- Sending and receiving chat messages.
- Online status aka presence of chat participants.
- Chat room occupancy, i.e total number of connections and presence members.
- Typing indicators
- Room-level reactions (ephemeral at this stage)
If there are other features you'd like us to prioritize, please [let us know](https://forms.gle/mBw9M53NYuCBLFpMA).
## Usage

@@ -32,3 +62,3 @@

## Installation and authentication
## Installation

@@ -41,2 +71,18 @@ Install the Chat SDK:

For browsers, you can also include the Chat SDK directly into your HTML:
```html
<!-- Ably Chat also requires the core Ably SDK to be available -->
<script src="https://cdn.ably.com/lib/ably.min-2.js"></script>
<script src="https://cdn.ably.com/lib/ably-chat.umd.cjs-0.js"></script>
<script>
const realtime = new Ably.Realtime({key: 'your-ably-key'})
const chatClient = new AblyChat.ChatClient(realtime);
</script>
```
The Ably client library follows [Semantic Versioning](http://semver.org/). To lock into a major or minor version of the client library, you can specify a specific version number such as https://cdn.ably.com/lib/ably-chat-0.js for all v0._ versions, or https://cdn.ably.com/lib/ably-chat-0.1.js for all v0.1._ versions, or you can lock into a single release with https://cdn.ably.com/lib/ably-chat-0.1.0.js. See https://github.com/ably/ably-chat-js/tags for a list of tagged releases.
## Instantiation and authentication
To instantiate the Chat SDK, create an [Ably client](https://ably.com/docs/getting-started/setup) and pass it into the

@@ -66,3 +112,3 @@ Chat constructor:

## Current connection status
### Current connection status

@@ -76,3 +122,3 @@ You can view the current connection status at any time:

## Subscribing to connection status changes
### Subscribing to connection status changes

@@ -107,4 +153,10 @@ You can subscribe to connection status changes by registering a listener, like so:

The second argument to `rooms.get` is a `RoomOptions` argument, which tells the Chat SDK what features you would like your room to use and how they should be configured. For example, you can set the timeout between keystrokes for typing events as part of the room options. Sensible defaults are provided for your convenience.
The second argument to `rooms.get` is a `RoomOptions` argument, which tells the Chat SDK what features you would like your room to use and how they should be configured. For example, you can set the timeout between keystrokes for typing events as part of the room options. Sensible defaults for each
of the features are provided for your convenience:
- A typing timeout (time of inactivity before typing stops) of 10 seconds.
- Entry into, and subscription to, presence.
The defaults options for each feature may be viewed [here](https://github.com/ably/ably-chat-js/blob/main/src/RoomOptions.ts).
In order to use the same room but with different options, you must first `release` the room before requesting an instance with the changed options (see below for more information on releasing rooms).

@@ -209,21 +261,7 @@

### Message payload
```json5
{
timeserial: 'string',
clientId: 'string',
roomId: 'string',
text: 'string',
createdAt: 'number',
metadata: 'Record<string, unknown>',
headers: 'Record<string, number | string | boolean | null | undefined>',
}
```
### Metadata and headers for chat messages
**Metadata** is a map of extra information that can be attached to chat messages. Metadata is not used by Ably and is sent as part of the realtime message payload. Example use cases are setting custom styling (like background or text color or fonts), adding links to external images, emojis, etc.
**Metadata** is a map of extra information that can be attached to chat messages. `metadata` is not used by Ably and is sent as part of the realtime message payload. Example use cases are setting custom styling (like background or text color or fonts), adding links to external images, emojis, etc.
**Headers** are a flat key-value map and are sent as part of the realtime message's extras inside the headers property. They can serve similar purposes as metadata but they are read by Ably and can be used for things such as [subscription filters](https://faqs.ably.com/subscription-filters).
**Headers** are a flat key-value map and are sent as part of the realtime message's `extras` inside the `headers` property. They can serve similar purposes as `metadata` but they are read by Ably and can be used for things such as [subscription filters](https://faqs.ably.com/subscription-filters).

@@ -313,3 +351,3 @@ To pass headers and/or metadata when sending a chat message:

### Retrieve online members
### Retrieving online members

@@ -457,3 +495,3 @@ You can get the complete list of currently online or present members, their state and data, by calling the `presence.get` method.

### Unsubscribe from typing updates
### Unsubscribing from typing updates

@@ -504,3 +542,3 @@ To unsubscribe the listener, you can call the corresponding `unsubscribe` method returned by the `subscribe` call:

### Retrieve the occupancy of a chat room
### Retrieving the occupancy of a chat room

@@ -525,6 +563,6 @@ You can request the current occupancy of a chat room using the `occupancy.get` method:

You can also add any metadata to reactions:
You can also add any metadata and headers to reactions:
```ts
await room.reactions.send({ type: 'like', metadata: { effect: 'fireworks' } });
await room.reactions.send({ type: 'like', metadata: { effect: 'fireworks' }, headers: { streamId: 'basketball-stream' } });
```

@@ -557,2 +595,100 @@

## Handling encoded objects in integrations
If you have set up an [Ably integration](https://ably.com/docs/general/integrations) to receive events from your chat
room, depending on your configuration, you may receive these as encoded objects.
See [here](https://ably.com/docs/general/webhooks) for more information.
Should you wish to convert this object to a chat type, you can use the functions below to help you.
For example, if you have the following item reach your integration:
```json
{
"items": [
{
"webhookId": "Ja-tsg",
"source": "channel.message",
"serial": "108iZpUxQBe4Vv35120919@1720954404104-0",
"timestamp": 1720954404104,
"name": "channel.message",
"data": {
"channelId": "some-room::$chat::$chatMessages",
"site": "eu-west-1-A",
"messages": [
{
"id": "chat:6TP2sA:some-room:a4534b0ab37bdd5:0",
"clientId": "user1",
"timestamp": 1720954404104,
"encoding": "json",
"extras": {
"timeserial": "108iZpUxQBe4Vv35120919@1720954404104-0",
"headers": {}
},
"data": "{\"text\":\"some text data\",\"metadata\":{}}",
"name": "message.created"
}
]
}
}
]
}
```
You should use `getEntityTypeFromEncoded` to first retrieve the chat entity type of the encoded message,
then call either `chatMessageFromEncoded` or `reactionFromEncoded` depending on the entity type.
```ts
import { getEntityTypeFromEncoded, chatMessageFromEncoded, reactionFromEncoded, ChatEntityType } from '@ably/chat';
integrationMessage.items.forEach((item) => {
item.data.messages.forEach(async (encodedMessage) => {
const entityType = getEntityTypeFromEncoded(encodedMessage);
switch (entityType) {
case ChatEntityType.ChatMessage:
const chatMessage = await chatMessageFromEncoded(encodedMessage);
console.log(chatMessage);
break;
case ChatEntityType.Reaction:
const reaction = await reactionFromEncoded(encodedMessage);
console.log(reaction);
break;
default:
console.log('Unknown entity type');
}
});
});
```
## Handling Ably messages
If you are working with the underlying channels directly and not using the Chat SDK, you can use these functions to
convert an inbound Ably message to a chat type.
You can use `getEntityTypeFromAblyMessage` to retrieve the chat entity type of the message,
then call either `chatMessageFromAblyMessage` or `reactionFromAblyMessage` depending on the entity type.
```ts
import * as Ably from 'ably';
import {
getEntityTypeFromAblyMessage,
chatMessageFromAblyMessage,
reactionFromAblyMessage,
ChatEntityType
} from '@ably/chat';
const entityType = getEntityTypeFromAblyMessage(inboundMessage as Ably.InboundMessage);
switch (entityType) {
case ChatEntityType.ChatMessage:
const chatMessage = chatMessageFromAblyMessage(inboundMessage as Ably.InboundMessage);
console.log(chatMessage);
break;
case ChatEntityType.Reaction:
const reaction = reactionFromAblyMessage(inboundMessage as Ably.InboundMessage);
console.log(reaction);
break;
default:
console.log('Unknown entity type');
}
```
## In-depth

@@ -587,2 +723,12 @@

## Contributing
For guidance on how to contribute to this project, see the [contributing guidelines](CONTRIBUTING.md).
## Support, feedback and troubleshooting
Please visit http://support.ably.com/ for access to our knowledge base and to ask for any assistance. You can also view the [community reported Github issues](https://github.com/ably/ably-chat-js/issues) or raise one yourself.
To see what has changed in recent versions, see the [changelog](CHANGELOG.md).
## Further reading

@@ -589,0 +735,0 @@

{
"include": ["./src/**/*.ts"],
"exclude": ["./test/**/*.test.tsx", "./test/**/*.test.ts"],
"include": [
"./hooks/**/*.ts",
"./hooks/**/*.tsx",
"index.ts",
"./types/**/*.ts",
"./types/**/*.tsx",
"./contexts/**/*.ts",
"./contexts/**/*.tsx",
"./providers/**/*.ts",
"./providers/**/*.tsx"
],
"compilerOptions": {
"rootDir": "./src",
"rootDir": ".",
"target": "es6",

@@ -7,0 +16,0 @@ "sourceMap": true,

@@ -13,4 +13,4 @@ // vite.config.js

lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'ably-chat-react',
entry: resolve(__dirname, 'index.ts'),
name: 'AblyChatReact',
fileName: 'ably-chat-react',

@@ -21,3 +21,6 @@ },

// not bundle it.
external: ['ably', '@ably/chat', 'react'],
// We don't bundle react, react-dom, and react/jsx-runtime as they are expected to be
// provided by the consuming application and not specifying them could lead to
// multiple conflicting versions of react.
external: ['ably', '@ably/chat', 'react', 'react-dom', 'react/jsx-runtime'],
},

@@ -24,0 +27,0 @@ sourcemap: true,

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc