fix price and modular

This commit is contained in:
2026-02-01 17:38:39 +03:30
parent 55f34aa15d
commit 164542d3e2
801 changed files with 16129 additions and 14478 deletions
@@ -1,47 +1,12 @@
"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 tslib_1 = require("tslib");
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 push_1 = tslib_1.__importDefault(require("./lib/push"));
const timer_1 = tslib_1.__importDefault(require("./lib/timer"));
const RealtimePresence_1 = tslib_1.__importDefault(require("./RealtimePresence"));
const Transformers = tslib_1.__importStar(require("./lib/transformers"));
const transformers_1 = require("./lib/transformers");
var REALTIME_POSTGRES_CHANGES_LISTEN_EVENT;
(function (REALTIME_POSTGRES_CHANGES_LISTEN_EVENT) {
@@ -71,9 +36,26 @@ exports.REALTIME_CHANNEL_STATES = constants_1.CHANNEL_STATES;
* and send and receive messages.
*/
class RealtimeChannel {
/**
* Creates a channel that can broadcast messages, sync presence, and listen to Postgres changes.
*
* The topic determines which realtime stream you are subscribing to. Config options let you
* enable acknowledgement for broadcasts, presence tracking, or private channels.
*
* @example
* ```ts
* import RealtimeClient from '@supabase/realtime-js'
*
* const client = new RealtimeClient('https://xyzcompany.supabase.co/realtime/v1', {
* params: { apikey: 'public-anon-key' },
* })
* const channel = new RealtimeChannel('realtime:public:messages', { config: {} }, client)
* ```
*/
constructor(
/** Topic name can be any string. */
topic, params = { config: {} }, socket) {
var _a, _b;
this.topic = topic;
this.params = params;
this.socket = socket;
@@ -132,18 +114,22 @@ class RealtimeChannel {
this.presence = new RealtimePresence_1.default(this);
this.broadcastEndpointURL = (0, transformers_1.httpEndpointURL)(this.socket.endPoint);
this.private = this.params.config.private || false;
if (!this.private && ((_b = (_a = this.params.config) === null || _a === void 0 ? void 0 : _a.broadcast) === null || _b === void 0 ? void 0 : _b.replay)) {
throw `tried to use replay on public channel '${this.topic}'. It must be a private channel.`;
}
}
/** Subscribe registers your client with the server */
subscribe(callback, timeout = this.timeout) {
var _a, _b;
var _a, _b, _c;
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 presence_enabled = (!!this.bindings[REALTIME_LISTEN_TYPES.PRESENCE] &&
this.bindings[REALTIME_LISTEN_TYPES.PRESENCE].length > 0) ||
((_c = this.params.config.presence) === null || _c === void 0 ? void 0 : _c.enabled) === true;
const accessTokenPayload = {};
const config = {
broadcast,
@@ -162,7 +148,10 @@ class RealtimeChannel {
this.joinPush
.receive('ok', async ({ postgres_changes }) => {
var _a;
this.socket.setAuth();
// Only refresh auth if using callback-based tokens
if (!this.socket._isManualToken()) {
this.socket.setAuth();
}
if (postgres_changes === undefined) {
callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.SUBSCRIBED);
return;
@@ -177,9 +166,9 @@ class RealtimeChannel {
const serverPostgresFilter = postgres_changes && postgres_changes[i];
if (serverPostgresFilter &&
serverPostgresFilter.event === event &&
serverPostgresFilter.schema === schema &&
serverPostgresFilter.table === table &&
serverPostgresFilter.filter === filter) {
RealtimeChannel.isFilterValueEqual(serverPostgresFilter.schema, schema) &&
RealtimeChannel.isFilterValueEqual(serverPostgresFilter.table, table) &&
RealtimeChannel.isFilterValueEqual(serverPostgresFilter.filter, filter)) {
newPostgresBindings.push(Object.assign(Object.assign({}, clientPostgresBinding), { id: serverPostgresFilter.id }));
}
else {
@@ -206,9 +195,19 @@ class RealtimeChannel {
}
return this;
}
/**
* Returns the current presence state for this channel.
*
* The shape is a map keyed by presence key (for example a user id) where each entry contains the
* tracked metadata for that user.
*/
presenceState() {
return this.presence.state;
}
/**
* Sends the supplied payload to the presence tracker so other subscribers can see that this
* client is online. Use `untrack` to stop broadcasting presence for the same key.
*/
async track(payload, opts = {}) {
return await this.send({
type: 'presence',
@@ -216,6 +215,9 @@ class RealtimeChannel {
payload,
}, opts.timeout || this.timeout);
}
/**
* Removes the current presence state for this client.
*/
async untrack(opts = {}) {
return await this.send({
type: 'presence',
@@ -223,13 +225,61 @@ class RealtimeChannel {
}, opts);
}
on(type, filter, callback) {
if (this.state === constants_1.CHANNEL_STATES.joined &&
type === REALTIME_LISTEN_TYPES.PRESENCE) {
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());
this.unsubscribe().then(async () => await this.subscribe());
}
return this._on(type, filter, callback);
}
/**
* Sends a broadcast message explicitly via REST API.
*
* This method always uses the REST API endpoint regardless of WebSocket connection state.
* Useful when you want to guarantee REST delivery or when gradually migrating from implicit REST fallback.
*
* @param event The name of the broadcast event
* @param payload Payload to be sent (required)
* @param opts Options including timeout
* @returns Promise resolving to object with success status, and error details if failed
*/
async httpSend(event, payload, opts = {}) {
var _a;
if (payload === undefined || payload === null) {
return Promise.reject('Payload is required for httpSend()');
}
const headers = {
apikey: this.socket.apiKey ? this.socket.apiKey : '',
'Content-Type': 'application/json',
};
if (this.socket.accessTokenValue) {
headers['Authorization'] = `Bearer ${this.socket.accessTokenValue}`;
}
const options = {
method: 'POST',
headers,
body: JSON.stringify({
messages: [
{
topic: this.subTopic,
event,
payload: payload,
private: this.private,
},
],
}),
};
const response = await this._fetchWithTimeout(this.broadcastEndpointURL, options, (_a = opts.timeout) !== null && _a !== void 0 ? _a : this.timeout);
if (response.status === 202) {
return { success: true };
}
let errorMessage = response.statusText;
try {
const errorBody = await response.json();
errorMessage = errorBody.error || errorBody.message || errorMessage;
}
catch (_b) { }
return Promise.reject(new Error(errorMessage));
}
/**
* Sends a message into the channel.
*
@@ -242,17 +292,20 @@ class RealtimeChannel {
async send(args, opts = {}) {
var _a, _b;
if (!this._canPush() && args.type === 'broadcast') {
console.warn('Realtime send() is automatically falling back to REST API. ' +
'This behavior will be deprecated in the future. ' +
'Please use httpSend() explicitly for REST delivery.');
const { event, payload: endpoint_payload } = args;
const authorization = this.socket.accessTokenValue
? `Bearer ${this.socket.accessTokenValue}`
: '';
const headers = {
apikey: this.socket.apiKey ? this.socket.apiKey : '',
'Content-Type': 'application/json',
};
if (this.socket.accessTokenValue) {
headers['Authorization'] = `Bearer ${this.socket.accessTokenValue}`;
}
const options = {
method: 'POST',
headers: {
Authorization: authorization,
apikey: this.socket.apiKey ? this.socket.apiKey : '',
'Content-Type': 'application/json',
},
headers,
body: JSON.stringify({
messages: [
{
@@ -291,6 +344,10 @@ class RealtimeChannel {
});
}
}
/**
* Updates the payload that will be sent the next time the channel joins (reconnects).
* Useful for rotating access tokens or updating config without re-creating the channel.
*/
updateJoinPayload(payload) {
this.joinPush.updatePayload(payload);
}
@@ -416,8 +473,7 @@ class RealtimeChannel {
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);
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 {
@@ -430,13 +486,11 @@ class RealtimeChannel {
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())));
(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()));
return bindEvent === '*' || bindEvent === ((_f = payload === null || payload === void 0 ? void 0 : payload.event) === null || _f === void 0 ? void 0 : _f.toLocaleLowerCase());
}
}
else {
@@ -521,6 +575,16 @@ class RealtimeChannel {
}
return true;
}
/**
* Compares two optional filter values for equality.
* Treats undefined, null, and empty string as equivalent empty values.
* @internal
*/
static isFilterValueEqual(serverValue, clientValue) {
const normalizedServer = serverValue !== null && serverValue !== void 0 ? serverValue : undefined;
const normalizedClient = clientValue !== null && clientValue !== void 0 ? clientValue : undefined;
return normalizedServer === normalizedClient;
}
/** @internal */
_rejoinUntilConnected() {
this.rejoinTimer.scheduleTimeout();
+190 -85
View File
@@ -1,47 +1,12 @@
"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 tslib_1 = require("tslib");
const websocket_factory_1 = tslib_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 serializer_1 = tslib_1.__importDefault(require("./lib/serializer"));
const timer_1 = tslib_1.__importDefault(require("./lib/timer"));
const transformers_1 = require("./lib/transformers");
const RealtimeChannel_1 = __importDefault(require("./RealtimeChannel"));
const RealtimeChannel_1 = tslib_1.__importDefault(require("./RealtimeChannel"));
const noop = () => { };
// Connection-related constants
const CONNECTION_TIMEOUTS = {
@@ -68,6 +33,7 @@ class RealtimeClient {
* @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.heartbeatCallback The optional function to handle heartbeat status and latency.
* @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))
@@ -75,11 +41,22 @@ class RealtimeClient {
* @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.
* @param options.vsn The protocol version to use when connecting. Supported versions are "1.0.0" and "2.0.0". Defaults to "2.0.0".
* @example
* ```ts
* import RealtimeClient from '@supabase/realtime-js'
*
* const client = new RealtimeClient('https://xyzcompany.supabase.co/realtime/v1', {
* params: { apikey: 'public-anon-key' },
* })
* client.connect()
* ```
*/
constructor(endPoint, options) {
var _a;
this.accessTokenValue = null;
this.apiKey = null;
this._manuallySetToken = false;
this.channels = new Array();
this.endPoint = '';
this.httpEndpoint = '';
@@ -94,6 +71,7 @@ class RealtimeClient {
this.heartbeatCallback = noop;
this.ref = 0;
this.reconnectTimer = null;
this.vsn = constants_1.DEFAULT_VSN;
this.logger = noop;
this.conn = null;
this.sendBuffer = [];
@@ -108,28 +86,17 @@ class RealtimeClient {
this._connectionState = 'disconnected';
this._wasManualDisconnect = false;
this._authPromise = null;
this._heartbeatSentAt = null;
/**
* Use either custom fetch, if provided, or default fetch to make HTTP requests
*
* @internal
*/
this._resolveFetch = (customFetch) => {
let _fetch;
if (customFetch) {
_fetch = customFetch;
return (...args) => customFetch(...args);
}
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);
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)) {
@@ -154,7 +121,12 @@ class RealtimeClient {
return;
}
this._setConnectionState('connecting');
this._setAuthSafely('connect');
// Trigger auth if needed and not already in progress
// This ensures auth is called for standalone RealtimeClient usage
// while avoiding race conditions with SupabaseClient's immediate setAuth call
if (this.accessToken && !this._authPromise) {
this._setAuthSafely('connect');
}
// Establish WebSocket connection
if (this.transport) {
// Use custom transport if provided
@@ -191,7 +163,7 @@ class RealtimeClient {
* @returns string The URL of the websocket.
*/
endpointURL() {
return this._appendParams(this.endPoint, Object.assign({}, this.params, { vsn: constants_1.VSN }));
return this._appendParams(this.endPoint, Object.assign({}, this.params, { vsn: this.vsn }));
}
/**
* Disconnects the socket.
@@ -213,12 +185,14 @@ class RealtimeClient {
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();
// Close the WebSocket connection if close method exists
if (typeof this.conn.close === 'function') {
if (code) {
this.conn.close(code, reason !== null && reason !== void 0 ? reason : '');
}
else {
this.conn.close();
}
}
this._teardownConnection();
}
@@ -293,6 +267,13 @@ class RealtimeClient {
isDisconnecting() {
return this._connectionState === 'disconnecting';
}
/**
* Creates (or reuses) a {@link RealtimeChannel} for the provided topic.
*
* Topics are automatically prefixed with `realtime:` to match the Realtime service.
* If a channel with the same topic already exists it will be returned instead of creating
* a duplicate connection.
*/
channel(topic, params = { config: {} }) {
const realtimeTopic = `realtime:${topic}`;
const exists = this.getChannels().find((c) => c.topic === realtimeTopic);
@@ -333,7 +314,18 @@ class RealtimeClient {
*
* On callback used, it will set the value of the token internal to the client.
*
* When a token is explicitly provided, it will be preserved across channel operations
* (including removeChannel and resubscribe). The `accessToken` callback will not be
* invoked until `setAuth()` is called without arguments.
*
* @param token A JWT string to override the token set on the client.
*
* @example
* // Use a manual token (preserved across resubscribes, ignores accessToken callback)
* client.realtime.setAuth('my-custom-jwt')
*
* // Switch back to using the accessToken callback
* client.realtime.setAuth()
*/
async setAuth(token = null) {
this._authPromise = this._performAuth(token);
@@ -344,20 +336,39 @@ class RealtimeClient {
this._authPromise = null;
}
}
/**
* Returns true if the current access token was explicitly set via setAuth(token),
* false if it was obtained via the accessToken callback.
* @internal
*/
_isManualToken() {
return this._manuallySetToken;
}
/**
* Sends a heartbeat message if the socket is connected.
*/
async sendHeartbeat() {
var _a;
if (!this.isConnected()) {
this.heartbeatCallback('disconnected');
try {
this.heartbeatCallback('disconnected');
}
catch (e) {
this.log('error', 'error in heartbeat callback', e);
}
return;
}
// Handle heartbeat timeout and force reconnection if needed
if (this.pendingHeartbeatRef) {
this.pendingHeartbeatRef = null;
this._heartbeatSentAt = null;
this.log('transport', 'heartbeat timeout. Attempting to re-establish connection');
this.heartbeatCallback('timeout');
try {
this.heartbeatCallback('timeout');
}
catch (e) {
this.log('error', 'error in heartbeat callback', e);
}
// 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');
@@ -370,6 +381,7 @@ class RealtimeClient {
return;
}
// Send heartbeat message to server
this._heartbeatSentAt = Date.now();
this.pendingHeartbeatRef = this._makeRef();
this.push({
topic: 'phoenix',
@@ -377,9 +389,18 @@ class RealtimeClient {
payload: {},
ref: this.pendingHeartbeatRef,
});
this.heartbeatCallback('sent');
try {
this.heartbeatCallback('sent');
}
catch (e) {
this.log('error', 'error in heartbeat callback', e);
}
this._setAuthSafely('heartbeat');
}
/**
* Sets a callback that receives lifecycle events for internal heartbeat messages.
* Useful for instrumenting connection health (e.g. sent/ok/timeout/disconnected).
*/
onHeartbeat(callback) {
this.heartbeatCallback = callback;
}
@@ -433,11 +454,18 @@ class RealtimeClient {
_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) {
if (msg.topic === 'phoenix' &&
msg.event === 'phx_reply' &&
msg.ref &&
msg.ref === this.pendingHeartbeatRef) {
const latency = this._heartbeatSentAt ? Date.now() - this._heartbeatSentAt : undefined;
try {
this.heartbeatCallback(msg.payload.status === 'ok' ? 'ok' : 'error', latency);
}
catch (e) {
this.log('error', 'error in heartbeat callback', e);
}
this._heartbeatSentAt = null;
this.pendingHeartbeatRef = null;
}
// Log incoming message
@@ -490,6 +518,9 @@ class RealtimeClient {
this.conn.onerror = (error) => this._onConnError(error);
this.conn.onmessage = (event) => this._onConnMessage(event);
this.conn.onclose = (event) => this._onConnClose(event);
if (this.conn.readyState === constants_1.SOCKET_STATES.open) {
this._onConnOpen();
}
}
/**
* Teardown connection and cleanup resources
@@ -497,6 +528,15 @@ class RealtimeClient {
*/
_teardownConnection() {
if (this.conn) {
if (this.conn.readyState === constants_1.SOCKET_STATES.open ||
this.conn.readyState === constants_1.SOCKET_STATES.connecting) {
try {
this.conn.close();
}
catch (e) {
this.log('error', 'Error closing connection', e);
}
}
this.conn.onopen = null;
this.conn.onerror = null;
this.conn.onmessage = null;
@@ -504,13 +544,26 @@ class RealtimeClient {
this.conn = null;
}
this._clearAllTimers();
this._terminateWorker();
this.channels.forEach((channel) => channel.teardown());
}
/** @internal */
_onConnOpen() {
this._setConnectionState('connected');
this.log('transport', `connected to ${this.endpointURL()}`);
this.flushSendBuffer();
// Wait for any pending auth operations before flushing send buffer
// This ensures channel join messages include the correct access token
const authPromise = this._authPromise ||
(this.accessToken && !this.accessTokenValue ? this.setAuth() : Promise.resolve());
authPromise
.then(() => {
this.flushSendBuffer();
})
.catch((e) => {
this.log('error', 'error waiting for auth on connect', e);
// Proceed anyway to avoid hanging connections
this.flushSendBuffer();
});
this._clearTimer('reconnect');
if (!this.worker) {
this._startHeartbeat();
@@ -539,7 +592,7 @@ class RealtimeClient {
this.workerRef = new Worker(objectUrl);
this.workerRef.onerror = (error) => {
this.log('worker', 'worker error', error.message);
this.workerRef.terminate();
this._terminateWorker();
};
this.workerRef.onmessage = (event) => {
if (event.data.event === 'keepAlive') {
@@ -551,6 +604,17 @@ class RealtimeClient {
interval: this.heartbeatIntervalMs,
});
}
/**
* Terminate the Web Worker and clear the reference
* @internal
*/
_terminateWorker() {
if (this.workerRef) {
this.log('worker', 'terminating worker');
this.workerRef.terminate();
this.workerRef = undefined;
}
}
/** @internal */
_onConnClose(event) {
var _a;
@@ -570,6 +634,12 @@ class RealtimeClient {
this.log('transport', `${error}`);
this._triggerChanError();
this._triggerStateCallbacks('error', error);
try {
this.heartbeatCallback('error');
}
catch (e) {
this.log('error', 'error in heartbeat callback', e);
}
}
/** @internal */
_triggerChanError() {
@@ -614,16 +684,34 @@ class RealtimeClient {
*/
async _performAuth(token = null) {
let tokenToSend;
let isManualToken = false;
if (token) {
tokenToSend = token;
// Track if this is a manually-provided token
isManualToken = true;
}
else if (this.accessToken) {
// Always call the accessToken callback to get fresh token
tokenToSend = await this.accessToken();
// Call the accessToken callback to get fresh token
try {
tokenToSend = await this.accessToken();
}
catch (e) {
this.log('error', 'Error fetching access token from callback', e);
// Fall back to cached value if callback fails
tokenToSend = this.accessTokenValue;
}
}
else {
tokenToSend = this.accessTokenValue;
}
// Track whether this token was manually set or fetched via callback
if (isManualToken) {
this._manuallySetToken = true;
}
else if (this.accessToken) {
// If we used the callback, clear the manual flag
this._manuallySetToken = false;
}
if (this.accessTokenValue != tokenToSend) {
this.accessTokenValue = tokenToSend;
this.channels.forEach((channel) => {
@@ -654,9 +742,12 @@ class RealtimeClient {
* @internal
*/
_setAuthSafely(context = 'general') {
this.setAuth().catch((e) => {
this.log('error', `error setting auth in ${context}`, e);
});
// Only refresh auth if using callback-based tokens
if (!this._isManualToken()) {
this.setAuth().catch((e) => {
this.log('error', `Error setting auth in ${context}`, e);
});
}
}
/**
* Trigger state change callbacks with proper error handling
@@ -696,7 +787,7 @@ class RealtimeClient {
* @internal
*/
_initializeOptions(options) {
var _a, _b, _c, _d, _e, _f, _g, _h;
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
// 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;
@@ -704,6 +795,8 @@ class RealtimeClient {
(_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;
this.heartbeatCallback = (_f = options === null || options === void 0 ? void 0 : options.heartbeatCallback) !== null && _f !== void 0 ? _f : noop;
this.vsn = (_g = options === null || options === void 0 ? void 0 : options.vsn) !== null && _g !== void 0 ? _g : constants_1.DEFAULT_VSN;
// Handle special cases
if (options === null || options === void 0 ? void 0 : options.params)
this.params = options.params;
@@ -715,15 +808,27 @@ class RealtimeClient {
}
// Set up functions with defaults
this.reconnectAfterMs =
(_f = options === null || options === void 0 ? void 0 : options.reconnectAfterMs) !== null && _f !== void 0 ? _f : ((tries) => {
(_h = options === null || options === void 0 ? void 0 : options.reconnectAfterMs) !== null && _h !== void 0 ? _h : ((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);
switch (this.vsn) {
case constants_1.VSN_1_0_0:
this.encode =
(_j = options === null || options === void 0 ? void 0 : options.encode) !== null && _j !== void 0 ? _j : ((payload, callback) => {
return callback(JSON.stringify(payload));
});
this.decode =
(_k = options === null || options === void 0 ? void 0 : options.decode) !== null && _k !== void 0 ? _k : ((payload, callback) => {
return callback(JSON.parse(payload));
});
break;
case constants_1.VSN_2_0_0:
this.encode = (_l = options === null || options === void 0 ? void 0 : options.encode) !== null && _l !== void 0 ? _l : this.serializer.encode.bind(this.serializer);
this.decode = (_m = options === null || options === void 0 ? void 0 : options.decode) !== null && _m !== void 0 ? _m : this.serializer.decode.bind(this.serializer);
break;
default:
throw new Error(`Unsupported serializer version: ${this.vsn}`);
}
// Handle worker setup
if (this.worker) {
if (typeof window !== 'undefined' && !window.Worker) {
@@ -13,11 +13,19 @@ var REALTIME_PRESENCE_LISTEN_EVENTS;
})(REALTIME_PRESENCE_LISTEN_EVENTS || (exports.REALTIME_PRESENCE_LISTEN_EVENTS = REALTIME_PRESENCE_LISTEN_EVENTS = {}));
class RealtimePresence {
/**
* Initializes the Presence.
* Creates a Presence helper that keeps the local presence state in sync with the server.
*
* @param channel - The RealtimeChannel
* @param opts - The options,
* for example `{events: {state: 'state', diff: 'diff'}}`
* @param channel - The realtime channel to bind to.
* @param opts - Optional custom event names, e.g. `{ events: { state: 'state', diff: 'diff' } }`.
*
* @example
* ```ts
* const presence = new RealtimePresence(channel)
*
* channel.on('presence', ({ event, key }) => {
* console.log(`Presence ${event} on ${key}`)
* })
* ```
*/
constructor(channel, opts) {
this.channel = channel;
+5 -40
View File
@@ -1,53 +1,18 @@
"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"));
const tslib_1 = require("tslib");
const RealtimeClient_1 = tslib_1.__importDefault(require("./RealtimeClient"));
exports.RealtimeClient = RealtimeClient_1.default;
const RealtimeChannel_1 = __importStar(require("./RealtimeChannel"));
const RealtimeChannel_1 = tslib_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"));
const RealtimePresence_1 = tslib_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"));
const websocket_factory_1 = tslib_1.__importDefault(require("./lib/websocket-factory"));
exports.WebSocketFactory = websocket_factory_1.default;
//# sourceMappingURL=index.js.map
@@ -1,9 +1,11 @@
"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;
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.DEFAULT_VSN = exports.VSN_2_0_0 = exports.VSN_1_0_0 = exports.DEFAULT_VERSION = void 0;
const version_1 = require("./version");
exports.DEFAULT_VERSION = `realtime-js/${version_1.version}`;
exports.VSN = '1.0.0';
exports.VSN_1_0_0 = '1.0.0';
exports.VSN_2_0_0 = '2.0.0';
exports.DEFAULT_VSN = exports.VSN_2_0_0;
exports.VERSION = version_1.version;
exports.DEFAULT_TIMEOUT = 10000;
exports.WS_CLOSE_NORMAL = 1000;
+2 -4
View File
@@ -91,10 +91,8 @@ class Push {
clearTimeout(this.timeoutTimer);
this.timeoutTimer = undefined;
}
_matchReceive({ status, response, }) {
this.recHooks
.filter((h) => h.status === status)
.forEach((h) => h.callback(response));
_matchReceive({ status, response }) {
this.recHooks.filter((h) => h.status === status).forEach((h) => h.callback(response));
}
_hasReceived(status) {
return this.receivedResp && this.receivedResp.status === status;
+133 -14
View File
@@ -1,35 +1,154 @@
"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() {
constructor(allowedMetadataKeys) {
this.HEADER_LENGTH = 1;
this.USER_BROADCAST_PUSH_META_LENGTH = 6;
this.KINDS = { userBroadcastPush: 3, userBroadcast: 4 };
this.BINARY_ENCODING = 0;
this.JSON_ENCODING = 1;
this.BROADCAST_EVENT = 'broadcast';
this.allowedMetadataKeys = [];
this.allowedMetadataKeys = allowedMetadataKeys !== null && allowedMetadataKeys !== void 0 ? allowedMetadataKeys : [];
}
encode(msg, callback) {
if (msg.event === this.BROADCAST_EVENT &&
!(msg.payload instanceof ArrayBuffer) &&
typeof msg.payload.event === 'string') {
return callback(this._binaryEncodeUserBroadcastPush(msg));
}
let payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload];
return callback(JSON.stringify(payload));
}
_binaryEncodeUserBroadcastPush(message) {
var _a;
if (this._isArrayBuffer((_a = message.payload) === null || _a === void 0 ? void 0 : _a.payload)) {
return this._encodeBinaryUserBroadcastPush(message);
}
else {
return this._encodeJsonUserBroadcastPush(message);
}
}
_encodeBinaryUserBroadcastPush(message) {
var _a, _b;
const userPayload = (_b = (_a = message.payload) === null || _a === void 0 ? void 0 : _a.payload) !== null && _b !== void 0 ? _b : new ArrayBuffer(0);
return this._encodeUserBroadcastPush(message, this.BINARY_ENCODING, userPayload);
}
_encodeJsonUserBroadcastPush(message) {
var _a, _b;
const userPayload = (_b = (_a = message.payload) === null || _a === void 0 ? void 0 : _a.payload) !== null && _b !== void 0 ? _b : {};
const encoder = new TextEncoder();
const encodedUserPayload = encoder.encode(JSON.stringify(userPayload)).buffer;
return this._encodeUserBroadcastPush(message, this.JSON_ENCODING, encodedUserPayload);
}
_encodeUserBroadcastPush(message, encodingType, encodedPayload) {
var _a, _b;
const topic = message.topic;
const ref = (_a = message.ref) !== null && _a !== void 0 ? _a : '';
const joinRef = (_b = message.join_ref) !== null && _b !== void 0 ? _b : '';
const userEvent = message.payload.event;
// Filter metadata based on allowed keys
const rest = this.allowedMetadataKeys
? this._pick(message.payload, this.allowedMetadataKeys)
: {};
const metadata = Object.keys(rest).length === 0 ? '' : JSON.stringify(rest);
// Validate lengths don't exceed uint8 max value (255)
if (joinRef.length > 255) {
throw new Error(`joinRef length ${joinRef.length} exceeds maximum of 255`);
}
if (ref.length > 255) {
throw new Error(`ref length ${ref.length} exceeds maximum of 255`);
}
if (topic.length > 255) {
throw new Error(`topic length ${topic.length} exceeds maximum of 255`);
}
if (userEvent.length > 255) {
throw new Error(`userEvent length ${userEvent.length} exceeds maximum of 255`);
}
if (metadata.length > 255) {
throw new Error(`metadata length ${metadata.length} exceeds maximum of 255`);
}
const metaLength = this.USER_BROADCAST_PUSH_META_LENGTH +
joinRef.length +
ref.length +
topic.length +
userEvent.length +
metadata.length;
const header = new ArrayBuffer(this.HEADER_LENGTH + metaLength);
let view = new DataView(header);
let offset = 0;
view.setUint8(offset++, this.KINDS.userBroadcastPush); // kind
view.setUint8(offset++, joinRef.length);
view.setUint8(offset++, ref.length);
view.setUint8(offset++, topic.length);
view.setUint8(offset++, userEvent.length);
view.setUint8(offset++, metadata.length);
view.setUint8(offset++, encodingType);
Array.from(joinRef, (char) => view.setUint8(offset++, char.charCodeAt(0)));
Array.from(ref, (char) => view.setUint8(offset++, char.charCodeAt(0)));
Array.from(topic, (char) => view.setUint8(offset++, char.charCodeAt(0)));
Array.from(userEvent, (char) => view.setUint8(offset++, char.charCodeAt(0)));
Array.from(metadata, (char) => view.setUint8(offset++, char.charCodeAt(0)));
var combined = new Uint8Array(header.byteLength + encodedPayload.byteLength);
combined.set(new Uint8Array(header), 0);
combined.set(new Uint8Array(encodedPayload), header.byteLength);
return combined.buffer;
}
decode(rawPayload, callback) {
if (rawPayload.constructor === ArrayBuffer) {
return callback(this._binaryDecode(rawPayload));
if (this._isArrayBuffer(rawPayload)) {
let result = this._binaryDecode(rawPayload);
return callback(result);
}
if (typeof rawPayload === 'string') {
return callback(JSON.parse(rawPayload));
const jsonPayload = JSON.parse(rawPayload);
const [join_ref, ref, topic, event, payload] = jsonPayload;
return callback({ join_ref, ref, topic, event, payload });
}
return callback({});
}
_binaryDecode(buffer) {
const view = new DataView(buffer);
const kind = view.getUint8(0);
const decoder = new TextDecoder();
return this._decodeBroadcast(buffer, view, decoder);
switch (kind) {
case this.KINDS.userBroadcast:
return this._decodeUserBroadcast(buffer, view, decoder);
}
}
_decodeBroadcast(buffer, view, decoder) {
_decodeUserBroadcast(buffer, view, decoder) {
const topicSize = view.getUint8(1);
const eventSize = view.getUint8(2);
let offset = this.HEADER_LENGTH + 2;
const userEventSize = view.getUint8(2);
const metadataSize = view.getUint8(3);
const payloadEncoding = view.getUint8(4);
let offset = this.HEADER_LENGTH + 4;
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 };
const userEvent = decoder.decode(buffer.slice(offset, offset + userEventSize));
offset = offset + userEventSize;
const metadata = decoder.decode(buffer.slice(offset, offset + metadataSize));
offset = offset + metadataSize;
const payload = buffer.slice(offset, buffer.byteLength);
const parsedPayload = payloadEncoding === this.JSON_ENCODING ? JSON.parse(decoder.decode(payload)) : payload;
const data = {
type: this.BROADCAST_EVENT,
event: userEvent,
payload: parsedPayload,
};
// Metadata is optional and always JSON encoded
if (metadataSize > 0) {
data['meta'] = JSON.parse(metadata);
}
return { join_ref: null, ref: null, topic: topic, event: this.BROADCAST_EVENT, payload: data };
}
_isArrayBuffer(buffer) {
var _a;
return buffer instanceof ArrayBuffer || ((_a = buffer === null || buffer === void 0 ? void 0 : buffer.constructor) === null || _a === void 0 ? void 0 : _a.name) === 'ArrayBuffer';
}
_pick(obj, keys) {
if (!obj || typeof obj !== 'object') {
return {};
}
return Object.fromEntries(Object.entries(obj).filter(([key]) => keys.includes(key)));
}
}
exports.default = Serializer;
@@ -48,6 +48,9 @@ var PostgresTypes;
const convertChangeData = (columns, record, options = {}) => {
var _a;
const skipTypes = (_a = options.skipTypes) !== null && _a !== void 0 ? _a : [];
if (!record) {
return {};
}
return Object.keys(record).reduce((acc, rec_key) => {
acc[rec_key] = (0, exports.convertColumn)(rec_key, columns, record, skipTypes);
return acc;
@@ -163,8 +166,7 @@ const toJson = (value) => {
try {
return JSON.parse(value);
}
catch (error) {
console.log(`JSON parse error: ${error}`);
catch (_a) {
return value;
}
}
@@ -220,10 +222,20 @@ const toTimestampString = (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';
const wsUrl = new URL(socketUrl);
wsUrl.protocol = wsUrl.protocol.replace(/^ws/i, 'http');
wsUrl.pathname = wsUrl.pathname
.replace(/\/+$/, '') // remove all trailing slashes
.replace(/\/socket\/websocket$/i, '') // remove the socket/websocket path
.replace(/\/socket$/i, '') // remove the socket path
.replace(/\/websocket$/i, ''); // remove the websocket path
if (wsUrl.pathname === '' || wsUrl.pathname === '/') {
wsUrl.pathname = '/api/broadcast';
}
else {
wsUrl.pathname = wsUrl.pathname + '/api/broadcast';
}
return wsUrl.href;
};
exports.httpEndpointURL = httpEndpointURL;
//# sourceMappingURL=transformers.js.map
@@ -1,5 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.version = void 0;
exports.version = '2.15.1';
// Generated automatically during releases by scripts/update-version-files.ts
// This file provides runtime access to the package version for:
// - HTTP request headers (e.g., X-Client-Info header for API requests)
// - Debugging and support (identifying which version is running)
// - Telemetry and logging (version reporting in errors/analytics)
// - Ensuring build artifacts match the published package version
exports.version = '2.93.3';
//# sourceMappingURL=version.js.map
@@ -1,18 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebSocketFactory = void 0;
/**
* Utilities for creating WebSocket instances across runtimes.
*/
class WebSocketFactory {
/**
* Static-only utility – prevent instantiation.
*/
constructor() { }
static detectEnvironment() {
var _a;
if (typeof WebSocket !== 'undefined') {
return { type: 'native', constructor: WebSocket };
}
if (typeof globalThis !== 'undefined' &&
typeof globalThis.WebSocket !== 'undefined') {
if (typeof globalThis !== 'undefined' && typeof globalThis.WebSocket !== 'undefined') {
return { type: 'native', constructor: globalThis.WebSocket };
}
if (typeof global !== 'undefined' &&
typeof global.WebSocket !== 'undefined') {
if (typeof global !== 'undefined' && typeof global.WebSocket !== 'undefined') {
return { type: 'native', constructor: global.WebSocket };
}
if (typeof globalThis !== 'undefined' &&
@@ -25,39 +30,43 @@ class WebSocketFactory {
};
}
if ((typeof globalThis !== 'undefined' && globalThis.EdgeRuntime) ||
(typeof navigator !== 'undefined' &&
((_a = navigator.userAgent) === null || _a === void 0 ? void 0 : _a.includes('Vercel-Edge')))) {
(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 };
// Use dynamic property access to avoid Next.js Edge Runtime static analysis warnings
const _process = globalThis['process'];
if (_process) {
const processVersions = _process['versions'];
if (processVersions && processVersions['node']) {
// Remove 'v' prefix if present and parse the major version
const versionString = processVersions['node'];
const nodeVersion = parseInt(versionString.replace(/^v/, '').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.',
};
}
// If not available, user needs to provide it
// Node.js < 22 doesn't have native WebSocket
return {
type: 'unsupported',
error: `Node.js ${nodeVersion} detected but native WebSocket not found.`,
workaround: 'Provide a WebSocket implementation via the transport option.',
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 })',
};
}
// 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',
@@ -65,6 +74,15 @@ class WebSocketFactory {
workaround: "Ensure you're running in a supported environment (browser, Node.js, Deno) or provide a custom WebSocket implementation.",
};
}
/**
* Returns the best available WebSocket constructor for the current runtime.
*
* @example
* ```ts
* const WS = WebSocketFactory.getWebSocketConstructor()
* const socket = new WS('wss://realtime.supabase.co/socket')
* ```
*/
static getWebSocketConstructor() {
const env = this.detectEnvironment();
if (env.constructor) {
@@ -76,10 +94,28 @@ class WebSocketFactory {
}
throw new Error(errorMessage);
}
/**
* Creates a WebSocket using the detected constructor.
*
* @example
* ```ts
* const socket = WebSocketFactory.createWebSocket('wss://realtime.supabase.co/socket')
* ```
*/
static createWebSocket(url, protocols) {
const WS = this.getWebSocketConstructor();
return new WS(url, protocols);
}
/**
* Detects whether the runtime can establish WebSocket connections.
*
* @example
* ```ts
* if (!WebSocketFactory.isWebSocketSupported()) {
* console.warn('Falling back to long polling')
* }
* ```
*/
static isWebSocketSupported() {
try {
const env = this.detectEnvironment();
+1
View File
@@ -0,0 +1 @@
../../../.nitro/tslib@2.8.1
+17 -22
View File
@@ -1,6 +1,6 @@
{
"name": "@supabase/realtime-js",
"version": "2.15.1",
"version": "2.93.3",
"description": "Listen to realtime updates to your PostgreSQL database",
"keywords": [
"realtime",
@@ -11,8 +11,8 @@
"firebase",
"supabase"
],
"homepage": "https://github.com/supabase/realtime-js",
"bugs": "https://github.com/supabase/realtime-js/issues",
"homepage": "https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js",
"bugs": "https://github.com/supabase/supabase-js/issues",
"files": [
"dist",
"src"
@@ -20,46 +20,41 @@
"main": "dist/main/index.js",
"module": "dist/module/index.js",
"types": "dist/module/index.d.ts",
"repository": "https://github.com/supabase/realtime-js",
"repository": {
"type": "git",
"url": "https://github.com/supabase/supabase-js.git",
"directory": "packages/core/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": "npm run build:main && npm run build:module",
"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",
"test:coverage": "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"
"test:ci": "vitest run --coverage"
},
"dependencies": {
"@supabase/node-fetch": "^2.6.13",
"@types/phoenix": "^1.6.6",
"ws": "^8.18.2",
"@types/ws": "^8.18.1"
"@types/ws": "^8.18.1",
"tslib": "2.8.1",
"ws": "^8.18.2"
},
"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"
},
"engines": {
"node": ">=20.0.0"
}
}