fix output

This commit is contained in:
2026-01-27 16:47:33 +03:30
parent c122b00647
commit a99c6aa051
4582 changed files with 389930 additions and 2514 deletions
@@ -0,0 +1,580 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.REALTIME_CHANNEL_STATES = exports.REALTIME_SUBSCRIBE_STATES = exports.REALTIME_LISTEN_TYPES = exports.REALTIME_POSTGRES_CHANGES_LISTEN_EVENT = void 0;
const constants_1 = require("./lib/constants");
const push_1 = __importDefault(require("./lib/push"));
const timer_1 = __importDefault(require("./lib/timer"));
const RealtimePresence_1 = __importDefault(require("./RealtimePresence"));
const Transformers = __importStar(require("./lib/transformers"));
const transformers_1 = require("./lib/transformers");
var REALTIME_POSTGRES_CHANGES_LISTEN_EVENT;
(function (REALTIME_POSTGRES_CHANGES_LISTEN_EVENT) {
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT["ALL"] = "*";
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT["INSERT"] = "INSERT";
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT["UPDATE"] = "UPDATE";
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT["DELETE"] = "DELETE";
})(REALTIME_POSTGRES_CHANGES_LISTEN_EVENT || (exports.REALTIME_POSTGRES_CHANGES_LISTEN_EVENT = REALTIME_POSTGRES_CHANGES_LISTEN_EVENT = {}));
var REALTIME_LISTEN_TYPES;
(function (REALTIME_LISTEN_TYPES) {
REALTIME_LISTEN_TYPES["BROADCAST"] = "broadcast";
REALTIME_LISTEN_TYPES["PRESENCE"] = "presence";
REALTIME_LISTEN_TYPES["POSTGRES_CHANGES"] = "postgres_changes";
REALTIME_LISTEN_TYPES["SYSTEM"] = "system";
})(REALTIME_LISTEN_TYPES || (exports.REALTIME_LISTEN_TYPES = REALTIME_LISTEN_TYPES = {}));
var REALTIME_SUBSCRIBE_STATES;
(function (REALTIME_SUBSCRIBE_STATES) {
REALTIME_SUBSCRIBE_STATES["SUBSCRIBED"] = "SUBSCRIBED";
REALTIME_SUBSCRIBE_STATES["TIMED_OUT"] = "TIMED_OUT";
REALTIME_SUBSCRIBE_STATES["CLOSED"] = "CLOSED";
REALTIME_SUBSCRIBE_STATES["CHANNEL_ERROR"] = "CHANNEL_ERROR";
})(REALTIME_SUBSCRIBE_STATES || (exports.REALTIME_SUBSCRIBE_STATES = REALTIME_SUBSCRIBE_STATES = {}));
exports.REALTIME_CHANNEL_STATES = constants_1.CHANNEL_STATES;
/** A channel is the basic building block of Realtime
* and narrows the scope of data flow to subscribed clients.
* You can think of a channel as a chatroom where participants are able to see who's online
* and send and receive messages.
*/
class RealtimeChannel {
constructor(
/** Topic name can be any string. */
topic, params = { config: {} }, socket) {
this.topic = topic;
this.params = params;
this.socket = socket;
this.bindings = {};
this.state = constants_1.CHANNEL_STATES.closed;
this.joinedOnce = false;
this.pushBuffer = [];
this.subTopic = topic.replace(/^realtime:/i, '');
this.params.config = Object.assign({
broadcast: { ack: false, self: false },
presence: { key: '', enabled: false },
private: false,
}, params.config);
this.timeout = this.socket.timeout;
this.joinPush = new push_1.default(this, constants_1.CHANNEL_EVENTS.join, this.params, this.timeout);
this.rejoinTimer = new timer_1.default(() => this._rejoinUntilConnected(), this.socket.reconnectAfterMs);
this.joinPush.receive('ok', () => {
this.state = constants_1.CHANNEL_STATES.joined;
this.rejoinTimer.reset();
this.pushBuffer.forEach((pushEvent) => pushEvent.send());
this.pushBuffer = [];
});
this._onClose(() => {
this.rejoinTimer.reset();
this.socket.log('channel', `close ${this.topic} ${this._joinRef()}`);
this.state = constants_1.CHANNEL_STATES.closed;
this.socket._remove(this);
});
this._onError((reason) => {
if (this._isLeaving() || this._isClosed()) {
return;
}
this.socket.log('channel', `error ${this.topic}`, reason);
this.state = constants_1.CHANNEL_STATES.errored;
this.rejoinTimer.scheduleTimeout();
});
this.joinPush.receive('timeout', () => {
if (!this._isJoining()) {
return;
}
this.socket.log('channel', `timeout ${this.topic}`, this.joinPush.timeout);
this.state = constants_1.CHANNEL_STATES.errored;
this.rejoinTimer.scheduleTimeout();
});
this.joinPush.receive('error', (reason) => {
if (this._isLeaving() || this._isClosed()) {
return;
}
this.socket.log('channel', `error ${this.topic}`, reason);
this.state = constants_1.CHANNEL_STATES.errored;
this.rejoinTimer.scheduleTimeout();
});
this._on(constants_1.CHANNEL_EVENTS.reply, {}, (payload, ref) => {
this._trigger(this._replyEventName(ref), payload);
});
this.presence = new RealtimePresence_1.default(this);
this.broadcastEndpointURL = (0, transformers_1.httpEndpointURL)(this.socket.endPoint);
this.private = this.params.config.private || false;
}
/** Subscribe registers your client with the server */
subscribe(callback, timeout = this.timeout) {
var _a, _b;
if (!this.socket.isConnected()) {
this.socket.connect();
}
if (this.state == constants_1.CHANNEL_STATES.closed) {
const { config: { broadcast, presence, private: isPrivate }, } = this.params;
const postgres_changes = (_b = (_a = this.bindings.postgres_changes) === null || _a === void 0 ? void 0 : _a.map((r) => r.filter)) !== null && _b !== void 0 ? _b : [];
const presence_enabled = !!this.bindings[REALTIME_LISTEN_TYPES.PRESENCE] &&
this.bindings[REALTIME_LISTEN_TYPES.PRESENCE].length > 0;
const accessTokenPayload = {};
const config = {
broadcast,
presence: Object.assign(Object.assign({}, presence), { enabled: presence_enabled }),
postgres_changes,
private: isPrivate,
};
if (this.socket.accessTokenValue) {
accessTokenPayload.access_token = this.socket.accessTokenValue;
}
this._onError((e) => callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR, e));
this._onClose(() => callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.CLOSED));
this.updateJoinPayload(Object.assign({ config }, accessTokenPayload));
this.joinedOnce = true;
this._rejoin(timeout);
this.joinPush
.receive('ok', async ({ postgres_changes }) => {
var _a;
this.socket.setAuth();
if (postgres_changes === undefined) {
callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.SUBSCRIBED);
return;
}
else {
const clientPostgresBindings = this.bindings.postgres_changes;
const bindingsLen = (_a = clientPostgresBindings === null || clientPostgresBindings === void 0 ? void 0 : clientPostgresBindings.length) !== null && _a !== void 0 ? _a : 0;
const newPostgresBindings = [];
for (let i = 0; i < bindingsLen; i++) {
const clientPostgresBinding = clientPostgresBindings[i];
const { filter: { event, schema, table, filter }, } = clientPostgresBinding;
const serverPostgresFilter = postgres_changes && postgres_changes[i];
if (serverPostgresFilter &&
serverPostgresFilter.event === event &&
serverPostgresFilter.schema === schema &&
serverPostgresFilter.table === table &&
serverPostgresFilter.filter === filter) {
newPostgresBindings.push(Object.assign(Object.assign({}, clientPostgresBinding), { id: serverPostgresFilter.id }));
}
else {
this.unsubscribe();
this.state = constants_1.CHANNEL_STATES.errored;
callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR, new Error('mismatch between server and client bindings for postgres changes'));
return;
}
}
this.bindings.postgres_changes = newPostgresBindings;
callback && callback(REALTIME_SUBSCRIBE_STATES.SUBSCRIBED);
return;
}
})
.receive('error', (error) => {
this.state = constants_1.CHANNEL_STATES.errored;
callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR, new Error(JSON.stringify(Object.values(error).join(', ') || 'error')));
return;
})
.receive('timeout', () => {
callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.TIMED_OUT);
return;
});
}
return this;
}
presenceState() {
return this.presence.state;
}
async track(payload, opts = {}) {
return await this.send({
type: 'presence',
event: 'track',
payload,
}, opts.timeout || this.timeout);
}
async untrack(opts = {}) {
return await this.send({
type: 'presence',
event: 'untrack',
}, opts);
}
on(type, filter, callback) {
if (this.state === constants_1.CHANNEL_STATES.joined &&
type === REALTIME_LISTEN_TYPES.PRESENCE) {
this.socket.log('channel', `resubscribe to ${this.topic} due to change in presence callbacks on joined channel`);
this.unsubscribe().then(() => this.subscribe());
}
return this._on(type, filter, callback);
}
/**
* Sends a message into the channel.
*
* @param args Arguments to send to channel
* @param args.type The type of event to send
* @param args.event The name of the event being sent
* @param args.payload Payload to be sent
* @param opts Options to be used during the send process
*/
async send(args, opts = {}) {
var _a, _b;
if (!this._canPush() && args.type === 'broadcast') {
const { event, payload: endpoint_payload } = args;
const authorization = this.socket.accessTokenValue
? `Bearer ${this.socket.accessTokenValue}`
: '';
const options = {
method: 'POST',
headers: {
Authorization: authorization,
apikey: this.socket.apiKey ? this.socket.apiKey : '',
'Content-Type': 'application/json',
},
body: JSON.stringify({
messages: [
{
topic: this.subTopic,
event,
payload: endpoint_payload,
private: this.private,
},
],
}),
};
try {
const response = await this._fetchWithTimeout(this.broadcastEndpointURL, options, (_a = opts.timeout) !== null && _a !== void 0 ? _a : this.timeout);
await ((_b = response.body) === null || _b === void 0 ? void 0 : _b.cancel());
return response.ok ? 'ok' : 'error';
}
catch (error) {
if (error.name === 'AbortError') {
return 'timed out';
}
else {
return 'error';
}
}
}
else {
return new Promise((resolve) => {
var _a, _b, _c;
const push = this._push(args.type, args, opts.timeout || this.timeout);
if (args.type === 'broadcast' && !((_c = (_b = (_a = this.params) === null || _a === void 0 ? void 0 : _a.config) === null || _b === void 0 ? void 0 : _b.broadcast) === null || _c === void 0 ? void 0 : _c.ack)) {
resolve('ok');
}
push.receive('ok', () => resolve('ok'));
push.receive('error', () => resolve('error'));
push.receive('timeout', () => resolve('timed out'));
});
}
}
updateJoinPayload(payload) {
this.joinPush.updatePayload(payload);
}
/**
* Leaves the channel.
*
* Unsubscribes from server events, and instructs channel to terminate on server.
* Triggers onClose() hooks.
*
* To receive leave acknowledgements, use the a `receive` hook to bind to the server ack, ie:
* channel.unsubscribe().receive("ok", () => alert("left!") )
*/
unsubscribe(timeout = this.timeout) {
this.state = constants_1.CHANNEL_STATES.leaving;
const onClose = () => {
this.socket.log('channel', `leave ${this.topic}`);
this._trigger(constants_1.CHANNEL_EVENTS.close, 'leave', this._joinRef());
};
this.joinPush.destroy();
let leavePush = null;
return new Promise((resolve) => {
leavePush = new push_1.default(this, constants_1.CHANNEL_EVENTS.leave, {}, timeout);
leavePush
.receive('ok', () => {
onClose();
resolve('ok');
})
.receive('timeout', () => {
onClose();
resolve('timed out');
})
.receive('error', () => {
resolve('error');
});
leavePush.send();
if (!this._canPush()) {
leavePush.trigger('ok', {});
}
}).finally(() => {
leavePush === null || leavePush === void 0 ? void 0 : leavePush.destroy();
});
}
/**
* Teardown the channel.
*
* Destroys and stops related timers.
*/
teardown() {
this.pushBuffer.forEach((push) => push.destroy());
this.pushBuffer = [];
this.rejoinTimer.reset();
this.joinPush.destroy();
this.state = constants_1.CHANNEL_STATES.closed;
this.bindings = {};
}
/** @internal */
async _fetchWithTimeout(url, options, timeout) {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
const response = await this.socket.fetch(url, Object.assign(Object.assign({}, options), { signal: controller.signal }));
clearTimeout(id);
return response;
}
/** @internal */
_push(event, payload, timeout = this.timeout) {
if (!this.joinedOnce) {
throw `tried to push '${event}' to '${this.topic}' before joining. Use channel.subscribe() before pushing events`;
}
let pushEvent = new push_1.default(this, event, payload, timeout);
if (this._canPush()) {
pushEvent.send();
}
else {
this._addToPushBuffer(pushEvent);
}
return pushEvent;
}
/** @internal */
_addToPushBuffer(pushEvent) {
pushEvent.startTimeout();
this.pushBuffer.push(pushEvent);
// Enforce buffer size limit
if (this.pushBuffer.length > constants_1.MAX_PUSH_BUFFER_SIZE) {
const removedPush = this.pushBuffer.shift();
if (removedPush) {
removedPush.destroy();
this.socket.log('channel', `discarded push due to buffer overflow: ${removedPush.event}`, removedPush.payload);
}
}
}
/**
* Overridable message hook
*
* Receives all events for specialized message handling before dispatching to the channel callbacks.
* Must return the payload, modified or unmodified.
*
* @internal
*/
_onMessage(_event, payload, _ref) {
return payload;
}
/** @internal */
_isMember(topic) {
return this.topic === topic;
}
/** @internal */
_joinRef() {
return this.joinPush.ref;
}
/** @internal */
_trigger(type, payload, ref) {
var _a, _b;
const typeLower = type.toLocaleLowerCase();
const { close, error, leave, join } = constants_1.CHANNEL_EVENTS;
const events = [close, error, leave, join];
if (ref && events.indexOf(typeLower) >= 0 && ref !== this._joinRef()) {
return;
}
let handledPayload = this._onMessage(typeLower, payload, ref);
if (payload && !handledPayload) {
throw 'channel onMessage callbacks must return the payload, modified or unmodified';
}
if (['insert', 'update', 'delete'].includes(typeLower)) {
(_a = this.bindings.postgres_changes) === null || _a === void 0 ? void 0 : _a.filter((bind) => {
var _a, _b, _c;
return (((_a = bind.filter) === null || _a === void 0 ? void 0 : _a.event) === '*' ||
((_c = (_b = bind.filter) === null || _b === void 0 ? void 0 : _b.event) === null || _c === void 0 ? void 0 : _c.toLocaleLowerCase()) === typeLower);
}).map((bind) => bind.callback(handledPayload, ref));
}
else {
(_b = this.bindings[typeLower]) === null || _b === void 0 ? void 0 : _b.filter((bind) => {
var _a, _b, _c, _d, _e, _f;
if (['broadcast', 'presence', 'postgres_changes'].includes(typeLower)) {
if ('id' in bind) {
const bindId = bind.id;
const bindEvent = (_a = bind.filter) === null || _a === void 0 ? void 0 : _a.event;
return (bindId &&
((_b = payload.ids) === null || _b === void 0 ? void 0 : _b.includes(bindId)) &&
(bindEvent === '*' ||
(bindEvent === null || bindEvent === void 0 ? void 0 : bindEvent.toLocaleLowerCase()) ===
((_c = payload.data) === null || _c === void 0 ? void 0 : _c.type.toLocaleLowerCase())));
}
else {
const bindEvent = (_e = (_d = bind === null || bind === void 0 ? void 0 : bind.filter) === null || _d === void 0 ? void 0 : _d.event) === null || _e === void 0 ? void 0 : _e.toLocaleLowerCase();
return (bindEvent === '*' ||
bindEvent === ((_f = payload === null || payload === void 0 ? void 0 : payload.event) === null || _f === void 0 ? void 0 : _f.toLocaleLowerCase()));
}
}
else {
return bind.type.toLocaleLowerCase() === typeLower;
}
}).map((bind) => {
if (typeof handledPayload === 'object' && 'ids' in handledPayload) {
const postgresChanges = handledPayload.data;
const { schema, table, commit_timestamp, type, errors } = postgresChanges;
const enrichedPayload = {
schema: schema,
table: table,
commit_timestamp: commit_timestamp,
eventType: type,
new: {},
old: {},
errors: errors,
};
handledPayload = Object.assign(Object.assign({}, enrichedPayload), this._getPayloadRecords(postgresChanges));
}
bind.callback(handledPayload, ref);
});
}
}
/** @internal */
_isClosed() {
return this.state === constants_1.CHANNEL_STATES.closed;
}
/** @internal */
_isJoined() {
return this.state === constants_1.CHANNEL_STATES.joined;
}
/** @internal */
_isJoining() {
return this.state === constants_1.CHANNEL_STATES.joining;
}
/** @internal */
_isLeaving() {
return this.state === constants_1.CHANNEL_STATES.leaving;
}
/** @internal */
_replyEventName(ref) {
return `chan_reply_${ref}`;
}
/** @internal */
_on(type, filter, callback) {
const typeLower = type.toLocaleLowerCase();
const binding = {
type: typeLower,
filter: filter,
callback: callback,
};
if (this.bindings[typeLower]) {
this.bindings[typeLower].push(binding);
}
else {
this.bindings[typeLower] = [binding];
}
return this;
}
/** @internal */
_off(type, filter) {
const typeLower = type.toLocaleLowerCase();
if (this.bindings[typeLower]) {
this.bindings[typeLower] = this.bindings[typeLower].filter((bind) => {
var _a;
return !(((_a = bind.type) === null || _a === void 0 ? void 0 : _a.toLocaleLowerCase()) === typeLower &&
RealtimeChannel.isEqual(bind.filter, filter));
});
}
return this;
}
/** @internal */
static isEqual(obj1, obj2) {
if (Object.keys(obj1).length !== Object.keys(obj2).length) {
return false;
}
for (const k in obj1) {
if (obj1[k] !== obj2[k]) {
return false;
}
}
return true;
}
/** @internal */
_rejoinUntilConnected() {
this.rejoinTimer.scheduleTimeout();
if (this.socket.isConnected()) {
this._rejoin();
}
}
/**
* Registers a callback that will be executed when the channel closes.
*
* @internal
*/
_onClose(callback) {
this._on(constants_1.CHANNEL_EVENTS.close, {}, callback);
}
/**
* Registers a callback that will be executed when the channel encounteres an error.
*
* @internal
*/
_onError(callback) {
this._on(constants_1.CHANNEL_EVENTS.error, {}, (reason) => callback(reason));
}
/**
* Returns `true` if the socket is connected and the channel has been joined.
*
* @internal
*/
_canPush() {
return this.socket.isConnected() && this._isJoined();
}
/** @internal */
_rejoin(timeout = this.timeout) {
if (this._isLeaving()) {
return;
}
this.socket._leaveOpenTopic(this.topic);
this.state = constants_1.CHANNEL_STATES.joining;
this.joinPush.resend(timeout);
}
/** @internal */
_getPayloadRecords(payload) {
const records = {
new: {},
old: {},
};
if (payload.type === 'INSERT' || payload.type === 'UPDATE') {
records.new = Transformers.convertChangeData(payload.columns, payload.record);
}
if (payload.type === 'UPDATE' || payload.type === 'DELETE') {
records.old = Transformers.convertChangeData(payload.columns, payload.old_record);
}
return records;
}
}
exports.default = RealtimeChannel;
//# sourceMappingURL=RealtimeChannel.js.map
@@ -0,0 +1,737 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const websocket_factory_1 = __importDefault(require("./lib/websocket-factory"));
const constants_1 = require("./lib/constants");
const serializer_1 = __importDefault(require("./lib/serializer"));
const timer_1 = __importDefault(require("./lib/timer"));
const transformers_1 = require("./lib/transformers");
const RealtimeChannel_1 = __importDefault(require("./RealtimeChannel"));
const noop = () => { };
// Connection-related constants
const CONNECTION_TIMEOUTS = {
HEARTBEAT_INTERVAL: 25000,
RECONNECT_DELAY: 10,
HEARTBEAT_TIMEOUT_FALLBACK: 100,
};
const RECONNECT_INTERVALS = [1000, 2000, 5000, 10000];
const DEFAULT_RECONNECT_FALLBACK = 10000;
const WORKER_SCRIPT = `
addEventListener("message", (e) => {
if (e.data.event === "start") {
setInterval(() => postMessage({ event: "keepAlive" }), e.data.interval);
}
});`;
class RealtimeClient {
/**
* Initializes the Socket.
*
* @param endPoint The string WebSocket endpoint, ie, "ws://example.com/socket", "wss://example.com", "/socket" (inherited host & protocol)
* @param httpEndpoint The string HTTP endpoint, ie, "https://example.com", "/" (inherited host & protocol)
* @param options.transport The Websocket Transport, for example WebSocket. This can be a custom implementation
* @param options.timeout The default timeout in milliseconds to trigger push timeouts.
* @param options.params The optional params to pass when connecting.
* @param options.headers Deprecated: headers cannot be set on websocket connections and this option will be removed in the future.
* @param options.heartbeatIntervalMs The millisec interval to send a heartbeat message.
* @param options.logger The optional function for specialized logging, ie: logger: (kind, msg, data) => { console.log(`${kind}: ${msg}`, data) }
* @param options.logLevel Sets the log level for Realtime
* @param options.encode The function to encode outgoing messages. Defaults to JSON: (payload, callback) => callback(JSON.stringify(payload))
* @param options.decode The function to decode incoming messages. Defaults to Serializer's decode.
* @param options.reconnectAfterMs he optional function that returns the millsec reconnect interval. Defaults to stepped backoff off.
* @param options.worker Use Web Worker to set a side flow. Defaults to false.
* @param options.workerUrl The URL of the worker script. Defaults to https://realtime.supabase.com/worker.js that includes a heartbeat event call to keep the connection alive.
*/
constructor(endPoint, options) {
var _a;
this.accessTokenValue = null;
this.apiKey = null;
this.channels = new Array();
this.endPoint = '';
this.httpEndpoint = '';
/** @deprecated headers cannot be set on websocket connections */
this.headers = {};
this.params = {};
this.timeout = constants_1.DEFAULT_TIMEOUT;
this.transport = null;
this.heartbeatIntervalMs = CONNECTION_TIMEOUTS.HEARTBEAT_INTERVAL;
this.heartbeatTimer = undefined;
this.pendingHeartbeatRef = null;
this.heartbeatCallback = noop;
this.ref = 0;
this.reconnectTimer = null;
this.logger = noop;
this.conn = null;
this.sendBuffer = [];
this.serializer = new serializer_1.default();
this.stateChangeCallbacks = {
open: [],
close: [],
error: [],
message: [],
};
this.accessToken = null;
this._connectionState = 'disconnected';
this._wasManualDisconnect = false;
this._authPromise = null;
/**
* Use either custom fetch, if provided, or default fetch to make HTTP requests
*
* @internal
*/
this._resolveFetch = (customFetch) => {
let _fetch;
if (customFetch) {
_fetch = customFetch;
}
else if (typeof fetch === 'undefined') {
// Node.js environment without native fetch
_fetch = (...args) => Promise.resolve(`${'@supabase/node-fetch'}`).then(s => __importStar(require(s))).then(({ default: fetch }) => fetch(...args))
.catch((error) => {
throw new Error(`Failed to load @supabase/node-fetch: ${error.message}. ` +
`This is required for HTTP requests in Node.js environments without native fetch.`);
});
}
else {
_fetch = fetch;
}
return (...args) => _fetch(...args);
};
// Validate required parameters
if (!((_a = options === null || options === void 0 ? void 0 : options.params) === null || _a === void 0 ? void 0 : _a.apikey)) {
throw new Error('API key is required to connect to Realtime');
}
this.apiKey = options.params.apikey;
// Initialize endpoint URLs
this.endPoint = `${endPoint}/${constants_1.TRANSPORTS.websocket}`;
this.httpEndpoint = (0, transformers_1.httpEndpointURL)(endPoint);
this._initializeOptions(options);
this._setupReconnectionTimer();
this.fetch = this._resolveFetch(options === null || options === void 0 ? void 0 : options.fetch);
}
/**
* Connects the socket, unless already connected.
*/
connect() {
// Skip if already connecting, disconnecting, or connected
if (this.isConnecting() ||
this.isDisconnecting() ||
(this.conn !== null && this.isConnected())) {
return;
}
this._setConnectionState('connecting');
this._setAuthSafely('connect');
// Establish WebSocket connection
if (this.transport) {
// Use custom transport if provided
this.conn = new this.transport(this.endpointURL());
}
else {
// Try to use native WebSocket
try {
this.conn = websocket_factory_1.default.createWebSocket(this.endpointURL());
}
catch (error) {
this._setConnectionState('disconnected');
const errorMessage = error.message;
// Provide helpful error message based on environment
if (errorMessage.includes('Node.js')) {
throw new Error(`${errorMessage}\n\n` +
'To use Realtime in Node.js, you need to provide a WebSocket implementation:\n\n' +
'Option 1: Use Node.js 22+ which has native WebSocket support\n' +
'Option 2: Install and provide the "ws" package:\n\n' +
' npm install ws\n\n' +
' import ws from "ws"\n' +
' const client = new RealtimeClient(url, {\n' +
' ...options,\n' +
' transport: ws\n' +
' })');
}
throw new Error(`WebSocket not available: ${errorMessage}`);
}
}
this._setupConnectionHandlers();
}
/**
* Returns the URL of the websocket.
* @returns string The URL of the websocket.
*/
endpointURL() {
return this._appendParams(this.endPoint, Object.assign({}, this.params, { vsn: constants_1.VSN }));
}
/**
* Disconnects the socket.
*
* @param code A numeric status code to send on disconnect.
* @param reason A custom reason for the disconnect.
*/
disconnect(code, reason) {
if (this.isDisconnecting()) {
return;
}
this._setConnectionState('disconnecting', true);
if (this.conn) {
// Setup fallback timer to prevent hanging in disconnecting state
const fallbackTimer = setTimeout(() => {
this._setConnectionState('disconnected');
}, 100);
this.conn.onclose = () => {
clearTimeout(fallbackTimer);
this._setConnectionState('disconnected');
};
// Close the WebSocket connection
if (code) {
this.conn.close(code, reason !== null && reason !== void 0 ? reason : '');
}
else {
this.conn.close();
}
this._teardownConnection();
}
else {
this._setConnectionState('disconnected');
}
}
/**
* Returns all created channels
*/
getChannels() {
return this.channels;
}
/**
* Unsubscribes and removes a single channel
* @param channel A RealtimeChannel instance
*/
async removeChannel(channel) {
const status = await channel.unsubscribe();
if (this.channels.length === 0) {
this.disconnect();
}
return status;
}
/**
* Unsubscribes and removes all channels
*/
async removeAllChannels() {
const values_1 = await Promise.all(this.channels.map((channel) => channel.unsubscribe()));
this.channels = [];
this.disconnect();
return values_1;
}
/**
* Logs the message.
*
* For customized logging, `this.logger` can be overridden.
*/
log(kind, msg, data) {
this.logger(kind, msg, data);
}
/**
* Returns the current state of the socket.
*/
connectionState() {
switch (this.conn && this.conn.readyState) {
case constants_1.SOCKET_STATES.connecting:
return constants_1.CONNECTION_STATE.Connecting;
case constants_1.SOCKET_STATES.open:
return constants_1.CONNECTION_STATE.Open;
case constants_1.SOCKET_STATES.closing:
return constants_1.CONNECTION_STATE.Closing;
default:
return constants_1.CONNECTION_STATE.Closed;
}
}
/**
* Returns `true` is the connection is open.
*/
isConnected() {
return this.connectionState() === constants_1.CONNECTION_STATE.Open;
}
/**
* Returns `true` if the connection is currently connecting.
*/
isConnecting() {
return this._connectionState === 'connecting';
}
/**
* Returns `true` if the connection is currently disconnecting.
*/
isDisconnecting() {
return this._connectionState === 'disconnecting';
}
channel(topic, params = { config: {} }) {
const realtimeTopic = `realtime:${topic}`;
const exists = this.getChannels().find((c) => c.topic === realtimeTopic);
if (!exists) {
const chan = new RealtimeChannel_1.default(`realtime:${topic}`, params, this);
this.channels.push(chan);
return chan;
}
else {
return exists;
}
}
/**
* Push out a message if the socket is connected.
*
* If the socket is not connected, the message gets enqueued within a local buffer, and sent out when a connection is next established.
*/
push(data) {
const { topic, event, payload, ref } = data;
const callback = () => {
this.encode(data, (result) => {
var _a;
(_a = this.conn) === null || _a === void 0 ? void 0 : _a.send(result);
});
};
this.log('push', `${topic} ${event} (${ref})`, payload);
if (this.isConnected()) {
callback();
}
else {
this.sendBuffer.push(callback);
}
}
/**
* Sets the JWT access token used for channel subscription authorization and Realtime RLS.
*
* If param is null it will use the `accessToken` callback function or the token set on the client.
*
* On callback used, it will set the value of the token internal to the client.
*
* @param token A JWT string to override the token set on the client.
*/
async setAuth(token = null) {
this._authPromise = this._performAuth(token);
try {
await this._authPromise;
}
finally {
this._authPromise = null;
}
}
/**
* Sends a heartbeat message if the socket is connected.
*/
async sendHeartbeat() {
var _a;
if (!this.isConnected()) {
this.heartbeatCallback('disconnected');
return;
}
// Handle heartbeat timeout and force reconnection if needed
if (this.pendingHeartbeatRef) {
this.pendingHeartbeatRef = null;
this.log('transport', 'heartbeat timeout. Attempting to re-establish connection');
this.heartbeatCallback('timeout');
// Force reconnection after heartbeat timeout
this._wasManualDisconnect = false;
(_a = this.conn) === null || _a === void 0 ? void 0 : _a.close(constants_1.WS_CLOSE_NORMAL, 'heartbeat timeout');
setTimeout(() => {
var _a;
if (!this.isConnected()) {
(_a = this.reconnectTimer) === null || _a === void 0 ? void 0 : _a.scheduleTimeout();
}
}, CONNECTION_TIMEOUTS.HEARTBEAT_TIMEOUT_FALLBACK);
return;
}
// Send heartbeat message to server
this.pendingHeartbeatRef = this._makeRef();
this.push({
topic: 'phoenix',
event: 'heartbeat',
payload: {},
ref: this.pendingHeartbeatRef,
});
this.heartbeatCallback('sent');
this._setAuthSafely('heartbeat');
}
onHeartbeat(callback) {
this.heartbeatCallback = callback;
}
/**
* Flushes send buffer
*/
flushSendBuffer() {
if (this.isConnected() && this.sendBuffer.length > 0) {
this.sendBuffer.forEach((callback) => callback());
this.sendBuffer = [];
}
}
/**
* Return the next message ref, accounting for overflows
*
* @internal
*/
_makeRef() {
let newRef = this.ref + 1;
if (newRef === this.ref) {
this.ref = 0;
}
else {
this.ref = newRef;
}
return this.ref.toString();
}
/**
* Unsubscribe from channels with the specified topic.
*
* @internal
*/
_leaveOpenTopic(topic) {
let dupChannel = this.channels.find((c) => c.topic === topic && (c._isJoined() || c._isJoining()));
if (dupChannel) {
this.log('transport', `leaving duplicate topic "${topic}"`);
dupChannel.unsubscribe();
}
}
/**
* Removes a subscription from the socket.
*
* @param channel An open subscription.
*
* @internal
*/
_remove(channel) {
this.channels = this.channels.filter((c) => c.topic !== channel.topic);
}
/** @internal */
_onConnMessage(rawMessage) {
this.decode(rawMessage.data, (msg) => {
// Handle heartbeat responses
if (msg.topic === 'phoenix' && msg.event === 'phx_reply') {
this.heartbeatCallback(msg.payload.status === 'ok' ? 'ok' : 'error');
}
// Handle pending heartbeat reference cleanup
if (msg.ref && msg.ref === this.pendingHeartbeatRef) {
this.pendingHeartbeatRef = null;
}
// Log incoming message
const { topic, event, payload, ref } = msg;
const refString = ref ? `(${ref})` : '';
const status = payload.status || '';
this.log('receive', `${status} ${topic} ${event} ${refString}`.trim(), payload);
// Route message to appropriate channels
this.channels
.filter((channel) => channel._isMember(topic))
.forEach((channel) => channel._trigger(event, payload, ref));
this._triggerStateCallbacks('message', msg);
});
}
/**
* Clear specific timer
* @internal
*/
_clearTimer(timer) {
var _a;
if (timer === 'heartbeat' && this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = undefined;
}
else if (timer === 'reconnect') {
(_a = this.reconnectTimer) === null || _a === void 0 ? void 0 : _a.reset();
}
}
/**
* Clear all timers
* @internal
*/
_clearAllTimers() {
this._clearTimer('heartbeat');
this._clearTimer('reconnect');
}
/**
* Setup connection handlers for WebSocket events
* @internal
*/
_setupConnectionHandlers() {
if (!this.conn)
return;
// Set binary type if supported (browsers and most WebSocket implementations)
if ('binaryType' in this.conn) {
;
this.conn.binaryType = 'arraybuffer';
}
this.conn.onopen = () => this._onConnOpen();
this.conn.onerror = (error) => this._onConnError(error);
this.conn.onmessage = (event) => this._onConnMessage(event);
this.conn.onclose = (event) => this._onConnClose(event);
}
/**
* Teardown connection and cleanup resources
* @internal
*/
_teardownConnection() {
if (this.conn) {
this.conn.onopen = null;
this.conn.onerror = null;
this.conn.onmessage = null;
this.conn.onclose = null;
this.conn = null;
}
this._clearAllTimers();
this.channels.forEach((channel) => channel.teardown());
}
/** @internal */
_onConnOpen() {
this._setConnectionState('connected');
this.log('transport', `connected to ${this.endpointURL()}`);
this.flushSendBuffer();
this._clearTimer('reconnect');
if (!this.worker) {
this._startHeartbeat();
}
else {
if (!this.workerRef) {
this._startWorkerHeartbeat();
}
}
this._triggerStateCallbacks('open');
}
/** @internal */
_startHeartbeat() {
this.heartbeatTimer && clearInterval(this.heartbeatTimer);
this.heartbeatTimer = setInterval(() => this.sendHeartbeat(), this.heartbeatIntervalMs);
}
/** @internal */
_startWorkerHeartbeat() {
if (this.workerUrl) {
this.log('worker', `starting worker for from ${this.workerUrl}`);
}
else {
this.log('worker', `starting default worker`);
}
const objectUrl = this._workerObjectUrl(this.workerUrl);
this.workerRef = new Worker(objectUrl);
this.workerRef.onerror = (error) => {
this.log('worker', 'worker error', error.message);
this.workerRef.terminate();
};
this.workerRef.onmessage = (event) => {
if (event.data.event === 'keepAlive') {
this.sendHeartbeat();
}
};
this.workerRef.postMessage({
event: 'start',
interval: this.heartbeatIntervalMs,
});
}
/** @internal */
_onConnClose(event) {
var _a;
this._setConnectionState('disconnected');
this.log('transport', 'close', event);
this._triggerChanError();
this._clearTimer('heartbeat');
// Only schedule reconnection if it wasn't a manual disconnect
if (!this._wasManualDisconnect) {
(_a = this.reconnectTimer) === null || _a === void 0 ? void 0 : _a.scheduleTimeout();
}
this._triggerStateCallbacks('close', event);
}
/** @internal */
_onConnError(error) {
this._setConnectionState('disconnected');
this.log('transport', `${error}`);
this._triggerChanError();
this._triggerStateCallbacks('error', error);
}
/** @internal */
_triggerChanError() {
this.channels.forEach((channel) => channel._trigger(constants_1.CHANNEL_EVENTS.error));
}
/** @internal */
_appendParams(url, params) {
if (Object.keys(params).length === 0) {
return url;
}
const prefix = url.match(/\?/) ? '&' : '?';
const query = new URLSearchParams(params);
return `${url}${prefix}${query}`;
}
_workerObjectUrl(url) {
let result_url;
if (url) {
result_url = url;
}
else {
const blob = new Blob([WORKER_SCRIPT], { type: 'application/javascript' });
result_url = URL.createObjectURL(blob);
}
return result_url;
}
/**
* Set connection state with proper state management
* @internal
*/
_setConnectionState(state, manual = false) {
this._connectionState = state;
if (state === 'connecting') {
this._wasManualDisconnect = false;
}
else if (state === 'disconnecting') {
this._wasManualDisconnect = manual;
}
}
/**
* Perform the actual auth operation
* @internal
*/
async _performAuth(token = null) {
let tokenToSend;
if (token) {
tokenToSend = token;
}
else if (this.accessToken) {
// Always call the accessToken callback to get fresh token
tokenToSend = await this.accessToken();
}
else {
tokenToSend = this.accessTokenValue;
}
if (this.accessTokenValue != tokenToSend) {
this.accessTokenValue = tokenToSend;
this.channels.forEach((channel) => {
const payload = {
access_token: tokenToSend,
version: constants_1.DEFAULT_VERSION,
};
tokenToSend && channel.updateJoinPayload(payload);
if (channel.joinedOnce && channel._isJoined()) {
channel._push(constants_1.CHANNEL_EVENTS.access_token, {
access_token: tokenToSend,
});
}
});
}
}
/**
* Wait for any in-flight auth operations to complete
* @internal
*/
async _waitForAuthIfNeeded() {
if (this._authPromise) {
await this._authPromise;
}
}
/**
* Safely call setAuth with standardized error handling
* @internal
*/
_setAuthSafely(context = 'general') {
this.setAuth().catch((e) => {
this.log('error', `error setting auth in ${context}`, e);
});
}
/**
* Trigger state change callbacks with proper error handling
* @internal
*/
_triggerStateCallbacks(event, data) {
try {
this.stateChangeCallbacks[event].forEach((callback) => {
try {
callback(data);
}
catch (e) {
this.log('error', `error in ${event} callback`, e);
}
});
}
catch (e) {
this.log('error', `error triggering ${event} callbacks`, e);
}
}
/**
* Setup reconnection timer with proper configuration
* @internal
*/
_setupReconnectionTimer() {
this.reconnectTimer = new timer_1.default(async () => {
setTimeout(async () => {
await this._waitForAuthIfNeeded();
if (!this.isConnected()) {
this.connect();
}
}, CONNECTION_TIMEOUTS.RECONNECT_DELAY);
}, this.reconnectAfterMs);
}
/**
* Initialize client options with defaults
* @internal
*/
_initializeOptions(options) {
var _a, _b, _c, _d, _e, _f, _g, _h;
// Set defaults
this.transport = (_a = options === null || options === void 0 ? void 0 : options.transport) !== null && _a !== void 0 ? _a : null;
this.timeout = (_b = options === null || options === void 0 ? void 0 : options.timeout) !== null && _b !== void 0 ? _b : constants_1.DEFAULT_TIMEOUT;
this.heartbeatIntervalMs =
(_c = options === null || options === void 0 ? void 0 : options.heartbeatIntervalMs) !== null && _c !== void 0 ? _c : CONNECTION_TIMEOUTS.HEARTBEAT_INTERVAL;
this.worker = (_d = options === null || options === void 0 ? void 0 : options.worker) !== null && _d !== void 0 ? _d : false;
this.accessToken = (_e = options === null || options === void 0 ? void 0 : options.accessToken) !== null && _e !== void 0 ? _e : null;
// Handle special cases
if (options === null || options === void 0 ? void 0 : options.params)
this.params = options.params;
if (options === null || options === void 0 ? void 0 : options.logger)
this.logger = options.logger;
if ((options === null || options === void 0 ? void 0 : options.logLevel) || (options === null || options === void 0 ? void 0 : options.log_level)) {
this.logLevel = options.logLevel || options.log_level;
this.params = Object.assign(Object.assign({}, this.params), { log_level: this.logLevel });
}
// Set up functions with defaults
this.reconnectAfterMs =
(_f = options === null || options === void 0 ? void 0 : options.reconnectAfterMs) !== null && _f !== void 0 ? _f : ((tries) => {
return RECONNECT_INTERVALS[tries - 1] || DEFAULT_RECONNECT_FALLBACK;
});
this.encode =
(_g = options === null || options === void 0 ? void 0 : options.encode) !== null && _g !== void 0 ? _g : ((payload, callback) => {
return callback(JSON.stringify(payload));
});
this.decode =
(_h = options === null || options === void 0 ? void 0 : options.decode) !== null && _h !== void 0 ? _h : this.serializer.decode.bind(this.serializer);
// Handle worker setup
if (this.worker) {
if (typeof window !== 'undefined' && !window.Worker) {
throw new Error('Web Worker is not supported');
}
this.workerUrl = options === null || options === void 0 ? void 0 : options.workerUrl;
}
}
}
exports.default = RealtimeClient;
//# sourceMappingURL=RealtimeClient.js.map
@@ -0,0 +1,229 @@
"use strict";
/*
This file draws heavily from https://github.com/phoenixframework/phoenix/blob/d344ec0a732ab4ee204215b31de69cf4be72e3bf/assets/js/phoenix/presence.js
License: https://github.com/phoenixframework/phoenix/blob/d344ec0a732ab4ee204215b31de69cf4be72e3bf/LICENSE.md
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.REALTIME_PRESENCE_LISTEN_EVENTS = void 0;
var REALTIME_PRESENCE_LISTEN_EVENTS;
(function (REALTIME_PRESENCE_LISTEN_EVENTS) {
REALTIME_PRESENCE_LISTEN_EVENTS["SYNC"] = "sync";
REALTIME_PRESENCE_LISTEN_EVENTS["JOIN"] = "join";
REALTIME_PRESENCE_LISTEN_EVENTS["LEAVE"] = "leave";
})(REALTIME_PRESENCE_LISTEN_EVENTS || (exports.REALTIME_PRESENCE_LISTEN_EVENTS = REALTIME_PRESENCE_LISTEN_EVENTS = {}));
class RealtimePresence {
/**
* Initializes the Presence.
*
* @param channel - The RealtimeChannel
* @param opts - The options,
* for example `{events: {state: 'state', diff: 'diff'}}`
*/
constructor(channel, opts) {
this.channel = channel;
this.state = {};
this.pendingDiffs = [];
this.joinRef = null;
this.enabled = false;
this.caller = {
onJoin: () => { },
onLeave: () => { },
onSync: () => { },
};
const events = (opts === null || opts === void 0 ? void 0 : opts.events) || {
state: 'presence_state',
diff: 'presence_diff',
};
this.channel._on(events.state, {}, (newState) => {
const { onJoin, onLeave, onSync } = this.caller;
this.joinRef = this.channel._joinRef();
this.state = RealtimePresence.syncState(this.state, newState, onJoin, onLeave);
this.pendingDiffs.forEach((diff) => {
this.state = RealtimePresence.syncDiff(this.state, diff, onJoin, onLeave);
});
this.pendingDiffs = [];
onSync();
});
this.channel._on(events.diff, {}, (diff) => {
const { onJoin, onLeave, onSync } = this.caller;
if (this.inPendingSyncState()) {
this.pendingDiffs.push(diff);
}
else {
this.state = RealtimePresence.syncDiff(this.state, diff, onJoin, onLeave);
onSync();
}
});
this.onJoin((key, currentPresences, newPresences) => {
this.channel._trigger('presence', {
event: 'join',
key,
currentPresences,
newPresences,
});
});
this.onLeave((key, currentPresences, leftPresences) => {
this.channel._trigger('presence', {
event: 'leave',
key,
currentPresences,
leftPresences,
});
});
this.onSync(() => {
this.channel._trigger('presence', { event: 'sync' });
});
}
/**
* Used to sync the list of presences on the server with the
* client's state.
*
* An optional `onJoin` and `onLeave` callback can be provided to
* react to changes in the client's local presences across
* disconnects and reconnects with the server.
*
* @internal
*/
static syncState(currentState, newState, onJoin, onLeave) {
const state = this.cloneDeep(currentState);
const transformedState = this.transformState(newState);
const joins = {};
const leaves = {};
this.map(state, (key, presences) => {
if (!transformedState[key]) {
leaves[key] = presences;
}
});
this.map(transformedState, (key, newPresences) => {
const currentPresences = state[key];
if (currentPresences) {
const newPresenceRefs = newPresences.map((m) => m.presence_ref);
const curPresenceRefs = currentPresences.map((m) => m.presence_ref);
const joinedPresences = newPresences.filter((m) => curPresenceRefs.indexOf(m.presence_ref) < 0);
const leftPresences = currentPresences.filter((m) => newPresenceRefs.indexOf(m.presence_ref) < 0);
if (joinedPresences.length > 0) {
joins[key] = joinedPresences;
}
if (leftPresences.length > 0) {
leaves[key] = leftPresences;
}
}
else {
joins[key] = newPresences;
}
});
return this.syncDiff(state, { joins, leaves }, onJoin, onLeave);
}
/**
* Used to sync a diff of presence join and leave events from the
* server, as they happen.
*
* Like `syncState`, `syncDiff` accepts optional `onJoin` and
* `onLeave` callbacks to react to a user joining or leaving from a
* device.
*
* @internal
*/
static syncDiff(state, diff, onJoin, onLeave) {
const { joins, leaves } = {
joins: this.transformState(diff.joins),
leaves: this.transformState(diff.leaves),
};
if (!onJoin) {
onJoin = () => { };
}
if (!onLeave) {
onLeave = () => { };
}
this.map(joins, (key, newPresences) => {
var _a;
const currentPresences = (_a = state[key]) !== null && _a !== void 0 ? _a : [];
state[key] = this.cloneDeep(newPresences);
if (currentPresences.length > 0) {
const joinedPresenceRefs = state[key].map((m) => m.presence_ref);
const curPresences = currentPresences.filter((m) => joinedPresenceRefs.indexOf(m.presence_ref) < 0);
state[key].unshift(...curPresences);
}
onJoin(key, currentPresences, newPresences);
});
this.map(leaves, (key, leftPresences) => {
let currentPresences = state[key];
if (!currentPresences)
return;
const presenceRefsToRemove = leftPresences.map((m) => m.presence_ref);
currentPresences = currentPresences.filter((m) => presenceRefsToRemove.indexOf(m.presence_ref) < 0);
state[key] = currentPresences;
onLeave(key, currentPresences, leftPresences);
if (currentPresences.length === 0)
delete state[key];
});
return state;
}
/** @internal */
static map(obj, func) {
return Object.getOwnPropertyNames(obj).map((key) => func(key, obj[key]));
}
/**
* Remove 'metas' key
* Change 'phx_ref' to 'presence_ref'
* Remove 'phx_ref' and 'phx_ref_prev'
*
* @example
* // returns {
* abc123: [
* { presence_ref: '2', user_id: 1 },
* { presence_ref: '3', user_id: 2 }
* ]
* }
* RealtimePresence.transformState({
* abc123: {
* metas: [
* { phx_ref: '2', phx_ref_prev: '1' user_id: 1 },
* { phx_ref: '3', user_id: 2 }
* ]
* }
* })
*
* @internal
*/
static transformState(state) {
state = this.cloneDeep(state);
return Object.getOwnPropertyNames(state).reduce((newState, key) => {
const presences = state[key];
if ('metas' in presences) {
newState[key] = presences.metas.map((presence) => {
presence['presence_ref'] = presence['phx_ref'];
delete presence['phx_ref'];
delete presence['phx_ref_prev'];
return presence;
});
}
else {
newState[key] = presences;
}
return newState;
}, {});
}
/** @internal */
static cloneDeep(obj) {
return JSON.parse(JSON.stringify(obj));
}
/** @internal */
onJoin(callback) {
this.caller.onJoin = callback;
}
/** @internal */
onLeave(callback) {
this.caller.onLeave = callback;
}
/** @internal */
onSync(callback) {
this.caller.onSync = callback;
}
/** @internal */
inPendingSyncState() {
return !this.joinRef || this.joinRef !== this.channel._joinRef();
}
}
exports.default = RealtimePresence;
//# sourceMappingURL=RealtimePresence.js.map
+53
View File
@@ -0,0 +1,53 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebSocketFactory = exports.REALTIME_CHANNEL_STATES = exports.REALTIME_SUBSCRIBE_STATES = exports.REALTIME_PRESENCE_LISTEN_EVENTS = exports.REALTIME_POSTGRES_CHANGES_LISTEN_EVENT = exports.REALTIME_LISTEN_TYPES = exports.RealtimeClient = exports.RealtimeChannel = exports.RealtimePresence = void 0;
const RealtimeClient_1 = __importDefault(require("./RealtimeClient"));
exports.RealtimeClient = RealtimeClient_1.default;
const RealtimeChannel_1 = __importStar(require("./RealtimeChannel"));
exports.RealtimeChannel = RealtimeChannel_1.default;
Object.defineProperty(exports, "REALTIME_LISTEN_TYPES", { enumerable: true, get: function () { return RealtimeChannel_1.REALTIME_LISTEN_TYPES; } });
Object.defineProperty(exports, "REALTIME_POSTGRES_CHANGES_LISTEN_EVENT", { enumerable: true, get: function () { return RealtimeChannel_1.REALTIME_POSTGRES_CHANGES_LISTEN_EVENT; } });
Object.defineProperty(exports, "REALTIME_SUBSCRIBE_STATES", { enumerable: true, get: function () { return RealtimeChannel_1.REALTIME_SUBSCRIBE_STATES; } });
Object.defineProperty(exports, "REALTIME_CHANNEL_STATES", { enumerable: true, get: function () { return RealtimeChannel_1.REALTIME_CHANNEL_STATES; } });
const RealtimePresence_1 = __importStar(require("./RealtimePresence"));
exports.RealtimePresence = RealtimePresence_1.default;
Object.defineProperty(exports, "REALTIME_PRESENCE_LISTEN_EVENTS", { enumerable: true, get: function () { return RealtimePresence_1.REALTIME_PRESENCE_LISTEN_EVENTS; } });
const websocket_factory_1 = __importDefault(require("./lib/websocket-factory"));
exports.WebSocketFactory = websocket_factory_1.default;
//# sourceMappingURL=index.js.map
@@ -0,0 +1,46 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CONNECTION_STATE = exports.TRANSPORTS = exports.CHANNEL_EVENTS = exports.CHANNEL_STATES = exports.SOCKET_STATES = exports.MAX_PUSH_BUFFER_SIZE = exports.WS_CLOSE_NORMAL = exports.DEFAULT_TIMEOUT = exports.VERSION = exports.VSN = exports.DEFAULT_VERSION = void 0;
const version_1 = require("./version");
exports.DEFAULT_VERSION = `realtime-js/${version_1.version}`;
exports.VSN = '1.0.0';
exports.VERSION = version_1.version;
exports.DEFAULT_TIMEOUT = 10000;
exports.WS_CLOSE_NORMAL = 1000;
exports.MAX_PUSH_BUFFER_SIZE = 100;
var SOCKET_STATES;
(function (SOCKET_STATES) {
SOCKET_STATES[SOCKET_STATES["connecting"] = 0] = "connecting";
SOCKET_STATES[SOCKET_STATES["open"] = 1] = "open";
SOCKET_STATES[SOCKET_STATES["closing"] = 2] = "closing";
SOCKET_STATES[SOCKET_STATES["closed"] = 3] = "closed";
})(SOCKET_STATES || (exports.SOCKET_STATES = SOCKET_STATES = {}));
var CHANNEL_STATES;
(function (CHANNEL_STATES) {
CHANNEL_STATES["closed"] = "closed";
CHANNEL_STATES["errored"] = "errored";
CHANNEL_STATES["joined"] = "joined";
CHANNEL_STATES["joining"] = "joining";
CHANNEL_STATES["leaving"] = "leaving";
})(CHANNEL_STATES || (exports.CHANNEL_STATES = CHANNEL_STATES = {}));
var CHANNEL_EVENTS;
(function (CHANNEL_EVENTS) {
CHANNEL_EVENTS["close"] = "phx_close";
CHANNEL_EVENTS["error"] = "phx_error";
CHANNEL_EVENTS["join"] = "phx_join";
CHANNEL_EVENTS["reply"] = "phx_reply";
CHANNEL_EVENTS["leave"] = "phx_leave";
CHANNEL_EVENTS["access_token"] = "access_token";
})(CHANNEL_EVENTS || (exports.CHANNEL_EVENTS = CHANNEL_EVENTS = {}));
var TRANSPORTS;
(function (TRANSPORTS) {
TRANSPORTS["websocket"] = "websocket";
})(TRANSPORTS || (exports.TRANSPORTS = TRANSPORTS = {}));
var CONNECTION_STATE;
(function (CONNECTION_STATE) {
CONNECTION_STATE["Connecting"] = "connecting";
CONNECTION_STATE["Open"] = "open";
CONNECTION_STATE["Closing"] = "closing";
CONNECTION_STATE["Closed"] = "closed";
})(CONNECTION_STATE || (exports.CONNECTION_STATE = CONNECTION_STATE = {}));
//# sourceMappingURL=constants.js.map
+104
View File
@@ -0,0 +1,104 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const constants_1 = require("../lib/constants");
class Push {
/**
* Initializes the Push
*
* @param channel The Channel
* @param event The event, for example `"phx_join"`
* @param payload The payload, for example `{user_id: 123}`
* @param timeout The push timeout in milliseconds
*/
constructor(channel, event, payload = {}, timeout = constants_1.DEFAULT_TIMEOUT) {
this.channel = channel;
this.event = event;
this.payload = payload;
this.timeout = timeout;
this.sent = false;
this.timeoutTimer = undefined;
this.ref = '';
this.receivedResp = null;
this.recHooks = [];
this.refEvent = null;
}
resend(timeout) {
this.timeout = timeout;
this._cancelRefEvent();
this.ref = '';
this.refEvent = null;
this.receivedResp = null;
this.sent = false;
this.send();
}
send() {
if (this._hasReceived('timeout')) {
return;
}
this.startTimeout();
this.sent = true;
this.channel.socket.push({
topic: this.channel.topic,
event: this.event,
payload: this.payload,
ref: this.ref,
join_ref: this.channel._joinRef(),
});
}
updatePayload(payload) {
this.payload = Object.assign(Object.assign({}, this.payload), payload);
}
receive(status, callback) {
var _a;
if (this._hasReceived(status)) {
callback((_a = this.receivedResp) === null || _a === void 0 ? void 0 : _a.response);
}
this.recHooks.push({ status, callback });
return this;
}
startTimeout() {
if (this.timeoutTimer) {
return;
}
this.ref = this.channel.socket._makeRef();
this.refEvent = this.channel._replyEventName(this.ref);
const callback = (payload) => {
this._cancelRefEvent();
this._cancelTimeout();
this.receivedResp = payload;
this._matchReceive(payload);
};
this.channel._on(this.refEvent, {}, callback);
this.timeoutTimer = setTimeout(() => {
this.trigger('timeout', {});
}, this.timeout);
}
trigger(status, response) {
if (this.refEvent)
this.channel._trigger(this.refEvent, { status, response });
}
destroy() {
this._cancelRefEvent();
this._cancelTimeout();
}
_cancelRefEvent() {
if (!this.refEvent) {
return;
}
this.channel._off(this.refEvent, {});
}
_cancelTimeout() {
clearTimeout(this.timeoutTimer);
this.timeoutTimer = undefined;
}
_matchReceive({ status, response, }) {
this.recHooks
.filter((h) => h.status === status)
.forEach((h) => h.callback(response));
}
_hasReceived(status) {
return this.receivedResp && this.receivedResp.status === status;
}
}
exports.default = Push;
//# sourceMappingURL=push.js.map
@@ -0,0 +1,36 @@
"use strict";
// This file draws heavily from https://github.com/phoenixframework/phoenix/commit/cf098e9cf7a44ee6479d31d911a97d3c7430c6fe
// License: https://github.com/phoenixframework/phoenix/blob/master/LICENSE.md
Object.defineProperty(exports, "__esModule", { value: true });
class Serializer {
constructor() {
this.HEADER_LENGTH = 1;
}
decode(rawPayload, callback) {
if (rawPayload.constructor === ArrayBuffer) {
return callback(this._binaryDecode(rawPayload));
}
if (typeof rawPayload === 'string') {
return callback(JSON.parse(rawPayload));
}
return callback({});
}
_binaryDecode(buffer) {
const view = new DataView(buffer);
const decoder = new TextDecoder();
return this._decodeBroadcast(buffer, view, decoder);
}
_decodeBroadcast(buffer, view, decoder) {
const topicSize = view.getUint8(1);
const eventSize = view.getUint8(2);
let offset = this.HEADER_LENGTH + 2;
const topic = decoder.decode(buffer.slice(offset, offset + topicSize));
offset = offset + topicSize;
const event = decoder.decode(buffer.slice(offset, offset + eventSize));
offset = offset + eventSize;
const data = JSON.parse(decoder.decode(buffer.slice(offset, buffer.byteLength)));
return { ref: null, topic: topic, event: event, payload: data };
}
}
exports.default = Serializer;
//# sourceMappingURL=serializer.js.map
@@ -0,0 +1,39 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Creates a timer that accepts a `timerCalc` function to perform calculated timeout retries, such as exponential backoff.
*
* @example
* let reconnectTimer = new Timer(() => this.connect(), function(tries){
* return [1000, 5000, 10000][tries - 1] || 10000
* })
* reconnectTimer.scheduleTimeout() // fires after 1000
* reconnectTimer.scheduleTimeout() // fires after 5000
* reconnectTimer.reset()
* reconnectTimer.scheduleTimeout() // fires after 1000
*/
class Timer {
constructor(callback, timerCalc) {
this.callback = callback;
this.timerCalc = timerCalc;
this.timer = undefined;
this.tries = 0;
this.callback = callback;
this.timerCalc = timerCalc;
}
reset() {
this.tries = 0;
clearTimeout(this.timer);
this.timer = undefined;
}
// Cancels any previous scheduleTimeout and schedules callback
scheduleTimeout() {
clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.tries = this.tries + 1;
this.callback();
}, this.timerCalc(this.tries + 1));
}
}
exports.default = Timer;
//# sourceMappingURL=timer.js.map
@@ -0,0 +1,229 @@
"use strict";
/**
* Helpers to convert the change Payload into native JS types.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.httpEndpointURL = exports.toTimestampString = exports.toArray = exports.toJson = exports.toNumber = exports.toBoolean = exports.convertCell = exports.convertColumn = exports.convertChangeData = exports.PostgresTypes = void 0;
// Adapted from epgsql (src/epgsql_binary.erl), this module licensed under
// 3-clause BSD found here: https://raw.githubusercontent.com/epgsql/epgsql/devel/LICENSE
var PostgresTypes;
(function (PostgresTypes) {
PostgresTypes["abstime"] = "abstime";
PostgresTypes["bool"] = "bool";
PostgresTypes["date"] = "date";
PostgresTypes["daterange"] = "daterange";
PostgresTypes["float4"] = "float4";
PostgresTypes["float8"] = "float8";
PostgresTypes["int2"] = "int2";
PostgresTypes["int4"] = "int4";
PostgresTypes["int4range"] = "int4range";
PostgresTypes["int8"] = "int8";
PostgresTypes["int8range"] = "int8range";
PostgresTypes["json"] = "json";
PostgresTypes["jsonb"] = "jsonb";
PostgresTypes["money"] = "money";
PostgresTypes["numeric"] = "numeric";
PostgresTypes["oid"] = "oid";
PostgresTypes["reltime"] = "reltime";
PostgresTypes["text"] = "text";
PostgresTypes["time"] = "time";
PostgresTypes["timestamp"] = "timestamp";
PostgresTypes["timestamptz"] = "timestamptz";
PostgresTypes["timetz"] = "timetz";
PostgresTypes["tsrange"] = "tsrange";
PostgresTypes["tstzrange"] = "tstzrange";
})(PostgresTypes || (exports.PostgresTypes = PostgresTypes = {}));
/**
* Takes an array of columns and an object of string values then converts each string value
* to its mapped type.
*
* @param {{name: String, type: String}[]} columns
* @param {Object} record
* @param {Object} options The map of various options that can be applied to the mapper
* @param {Array} options.skipTypes The array of types that should not be converted
*
* @example convertChangeData([{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age:'33'}, {})
* //=>{ first_name: 'Paul', age: 33 }
*/
const convertChangeData = (columns, record, options = {}) => {
var _a;
const skipTypes = (_a = options.skipTypes) !== null && _a !== void 0 ? _a : [];
return Object.keys(record).reduce((acc, rec_key) => {
acc[rec_key] = (0, exports.convertColumn)(rec_key, columns, record, skipTypes);
return acc;
}, {});
};
exports.convertChangeData = convertChangeData;
/**
* Converts the value of an individual column.
*
* @param {String} columnName The column that you want to convert
* @param {{name: String, type: String}[]} columns All of the columns
* @param {Object} record The map of string values
* @param {Array} skipTypes An array of types that should not be converted
* @return {object} Useless information
*
* @example convertColumn('age', [{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age: '33'}, [])
* //=> 33
* @example convertColumn('age', [{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age: '33'}, ['int4'])
* //=> "33"
*/
const convertColumn = (columnName, columns, record, skipTypes) => {
const column = columns.find((x) => x.name === columnName);
const colType = column === null || column === void 0 ? void 0 : column.type;
const value = record[columnName];
if (colType && !skipTypes.includes(colType)) {
return (0, exports.convertCell)(colType, value);
}
return noop(value);
};
exports.convertColumn = convertColumn;
/**
* If the value of the cell is `null`, returns null.
* Otherwise converts the string value to the correct type.
* @param {String} type A postgres column type
* @param {String} value The cell value
*
* @example convertCell('bool', 't')
* //=> true
* @example convertCell('int8', '10')
* //=> 10
* @example convertCell('_int4', '{1,2,3,4}')
* //=> [1,2,3,4]
*/
const convertCell = (type, value) => {
// if data type is an array
if (type.charAt(0) === '_') {
const dataType = type.slice(1, type.length);
return (0, exports.toArray)(value, dataType);
}
// If not null, convert to correct type.
switch (type) {
case PostgresTypes.bool:
return (0, exports.toBoolean)(value);
case PostgresTypes.float4:
case PostgresTypes.float8:
case PostgresTypes.int2:
case PostgresTypes.int4:
case PostgresTypes.int8:
case PostgresTypes.numeric:
case PostgresTypes.oid:
return (0, exports.toNumber)(value);
case PostgresTypes.json:
case PostgresTypes.jsonb:
return (0, exports.toJson)(value);
case PostgresTypes.timestamp:
return (0, exports.toTimestampString)(value); // Format to be consistent with PostgREST
case PostgresTypes.abstime: // To allow users to cast it based on Timezone
case PostgresTypes.date: // To allow users to cast it based on Timezone
case PostgresTypes.daterange:
case PostgresTypes.int4range:
case PostgresTypes.int8range:
case PostgresTypes.money:
case PostgresTypes.reltime: // To allow users to cast it based on Timezone
case PostgresTypes.text:
case PostgresTypes.time: // To allow users to cast it based on Timezone
case PostgresTypes.timestamptz: // To allow users to cast it based on Timezone
case PostgresTypes.timetz: // To allow users to cast it based on Timezone
case PostgresTypes.tsrange:
case PostgresTypes.tstzrange:
return noop(value);
default:
// Return the value for remaining types
return noop(value);
}
};
exports.convertCell = convertCell;
const noop = (value) => {
return value;
};
const toBoolean = (value) => {
switch (value) {
case 't':
return true;
case 'f':
return false;
default:
return value;
}
};
exports.toBoolean = toBoolean;
const toNumber = (value) => {
if (typeof value === 'string') {
const parsedValue = parseFloat(value);
if (!Number.isNaN(parsedValue)) {
return parsedValue;
}
}
return value;
};
exports.toNumber = toNumber;
const toJson = (value) => {
if (typeof value === 'string') {
try {
return JSON.parse(value);
}
catch (error) {
console.log(`JSON parse error: ${error}`);
return value;
}
}
return value;
};
exports.toJson = toJson;
/**
* Converts a Postgres Array into a native JS array
*
* @example toArray('{}', 'int4')
* //=> []
* @example toArray('{"[2021-01-01,2021-12-31)","(2021-01-01,2021-12-32]"}', 'daterange')
* //=> ['[2021-01-01,2021-12-31)', '(2021-01-01,2021-12-32]']
* @example toArray([1,2,3,4], 'int4')
* //=> [1,2,3,4]
*/
const toArray = (value, type) => {
if (typeof value !== 'string') {
return value;
}
const lastIdx = value.length - 1;
const closeBrace = value[lastIdx];
const openBrace = value[0];
// Confirm value is a Postgres array by checking curly brackets
if (openBrace === '{' && closeBrace === '}') {
let arr;
const valTrim = value.slice(1, lastIdx);
// TODO: find a better solution to separate Postgres array data
try {
arr = JSON.parse('[' + valTrim + ']');
}
catch (_) {
// WARNING: splitting on comma does not cover all edge cases
arr = valTrim ? valTrim.split(',') : [];
}
return arr.map((val) => (0, exports.convertCell)(type, val));
}
return value;
};
exports.toArray = toArray;
/**
* Fixes timestamp to be ISO-8601. Swaps the space between the date and time for a 'T'
* See https://github.com/supabase/supabase/issues/18
*
* @example toTimestampString('2019-09-10 00:00:00')
* //=> '2019-09-10T00:00:00'
*/
const toTimestampString = (value) => {
if (typeof value === 'string') {
return value.replace(' ', 'T');
}
return value;
};
exports.toTimestampString = toTimestampString;
const httpEndpointURL = (socketUrl) => {
let url = socketUrl;
url = url.replace(/^ws/i, 'http');
url = url.replace(/(\/socket\/websocket|\/socket|\/websocket)\/?$/i, '');
return url.replace(/\/+$/, '') + '/api/broadcast';
};
exports.httpEndpointURL = httpEndpointURL;
//# sourceMappingURL=transformers.js.map
@@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.version = void 0;
exports.version = '2.15.1';
//# sourceMappingURL=version.js.map
@@ -0,0 +1,95 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebSocketFactory = void 0;
class WebSocketFactory {
static detectEnvironment() {
var _a;
if (typeof WebSocket !== 'undefined') {
return { type: 'native', constructor: WebSocket };
}
if (typeof globalThis !== 'undefined' &&
typeof globalThis.WebSocket !== 'undefined') {
return { type: 'native', constructor: globalThis.WebSocket };
}
if (typeof global !== 'undefined' &&
typeof global.WebSocket !== 'undefined') {
return { type: 'native', constructor: global.WebSocket };
}
if (typeof globalThis !== 'undefined' &&
typeof globalThis.WebSocketPair !== 'undefined' &&
typeof globalThis.WebSocket === 'undefined') {
return {
type: 'cloudflare',
error: 'Cloudflare Workers detected. WebSocket clients are not supported in Cloudflare Workers.',
workaround: 'Use Cloudflare Workers WebSocket API for server-side WebSocket handling, or deploy to a different runtime.',
};
}
if ((typeof globalThis !== 'undefined' && globalThis.EdgeRuntime) ||
(typeof navigator !== 'undefined' &&
((_a = navigator.userAgent) === null || _a === void 0 ? void 0 : _a.includes('Vercel-Edge')))) {
return {
type: 'unsupported',
error: 'Edge runtime detected (Vercel Edge/Netlify Edge). WebSockets are not supported in edge functions.',
workaround: 'Use serverless functions or a different deployment target for WebSocket functionality.',
};
}
if (typeof process !== 'undefined' &&
process.versions &&
process.versions.node) {
const nodeVersion = parseInt(process.versions.node.split('.')[0]);
// Node.js 22+ should have native WebSocket
if (nodeVersion >= 22) {
// Check if native WebSocket is available (should be in Node.js 22+)
if (typeof globalThis.WebSocket !== 'undefined') {
return { type: 'native', constructor: globalThis.WebSocket };
}
// If not available, user needs to provide it
return {
type: 'unsupported',
error: `Node.js ${nodeVersion} detected but native WebSocket not found.`,
workaround: 'Provide a WebSocket implementation via the transport option.',
};
}
// Node.js < 22 doesn't have native WebSocket
return {
type: 'unsupported',
error: `Node.js ${nodeVersion} detected without native WebSocket support.`,
workaround: 'For Node.js < 22, install "ws" package and provide it via the transport option:\n' +
'import ws from "ws"\n' +
'new RealtimeClient(url, { transport: ws })',
};
}
return {
type: 'unsupported',
error: 'Unknown JavaScript runtime without WebSocket support.',
workaround: "Ensure you're running in a supported environment (browser, Node.js, Deno) or provide a custom WebSocket implementation.",
};
}
static getWebSocketConstructor() {
const env = this.detectEnvironment();
if (env.constructor) {
return env.constructor;
}
let errorMessage = env.error || 'WebSocket not supported in this environment.';
if (env.workaround) {
errorMessage += `\n\nSuggested solution: ${env.workaround}`;
}
throw new Error(errorMessage);
}
static createWebSocket(url, protocols) {
const WS = this.getWebSocketConstructor();
return new WS(url, protocols);
}
static isWebSocketSupported() {
try {
const env = this.detectEnvironment();
return env.type === 'native' || env.type === 'ws';
}
catch (_a) {
return false;
}
}
}
exports.WebSocketFactory = WebSocketFactory;
exports.default = WebSocketFactory;
//# sourceMappingURL=websocket-factory.js.map
+65
View File
@@ -0,0 +1,65 @@
{
"name": "@supabase/realtime-js",
"version": "2.15.1",
"description": "Listen to realtime updates to your PostgreSQL database",
"keywords": [
"realtime",
"phoenix",
"elixir",
"javascript",
"typescript",
"firebase",
"supabase"
],
"homepage": "https://github.com/supabase/realtime-js",
"bugs": "https://github.com/supabase/realtime-js/issues",
"files": [
"dist",
"src"
],
"main": "dist/main/index.js",
"module": "dist/module/index.js",
"types": "dist/module/index.d.ts",
"repository": "https://github.com/supabase/realtime-js",
"author": "Supabase",
"license": "MIT",
"scripts": {
"clean": "rimraf dist docs/v2",
"format": "prettier --write \"{src,test}/**/*.ts\"",
"build": "run-s clean format build:*",
"build:main": "tsc -p tsconfig.json",
"build:module": "tsc -p tsconfig.module.json",
"test": "vitest run",
"test:watch": "vitest",
"coverage": "vitest run --coverage",
"coverage:text": "vitest run --coverage.enabled true --coverage.reporter=text",
"docs": "typedoc src/index.ts --out docs/v2",
"docs:json": "typedoc --json docs/v2/spec.json --excludeExternals src/index.ts",
"check-exports": "attw --pack .",
"ci": "run-s test coverage"
},
"dependencies": {
"@supabase/node-fetch": "^2.6.13",
"@types/phoenix": "^1.6.6",
"ws": "^8.18.2",
"@types/ws": "^8.18.1"
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.16.4",
"@vitest/coverage-v8": "^3.1.4",
"eslint": "^9.27.0",
"esm": "^3.2.25",
"jsdom": "^16.7.0",
"jsdom-global": "3.0.0",
"jsonwebtoken": "^9.0.2",
"mock-socket": "^9.3.1",
"npm-run-all": "^4.1.5",
"nyc": "^15.1.0",
"prettier": "^2.8.8",
"semantic-release-plugin-update-version-in-files": "^1.1.0",
"typedoc": "^0.27.9",
"typescript": "^5.8.3",
"vitest": "^3.1.4",
"web-worker": "1.2.0"
}
}