fix price and modular
This commit is contained in:
+128
-64
@@ -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
@@ -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) {
|
||||
|
||||
+12
-4
@@ -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
@@ -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
|
||||
+4
-2
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
+18
-6
@@ -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
|
||||
+7
-1
@@ -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
|
||||
Generated
Vendored
+62
-26
@@ -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
@@ -0,0 +1 @@
|
||||
../../../.nitro/tslib@2.8.1
|
||||
+17
-22
@@ -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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user