1/5/1404
This commit is contained in:
+528
@@ -0,0 +1,528 @@
|
||||
"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 (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.REALTIME_CHANNEL_STATES = exports.REALTIME_SUBSCRIBE_STATES = exports.REALTIME_LISTEN_TYPES = exports.REALTIME_POSTGRES_CHANGES_LISTEN_EVENT = void 0;
|
||||
const constants_1 = require("./lib/constants");
|
||||
const push_1 = __importDefault(require("./lib/push"));
|
||||
const timer_1 = __importDefault(require("./lib/timer"));
|
||||
const RealtimePresence_1 = __importDefault(require("./RealtimePresence"));
|
||||
const Transformers = __importStar(require("./lib/transformers"));
|
||||
const transformers_1 = require("./lib/transformers");
|
||||
var REALTIME_POSTGRES_CHANGES_LISTEN_EVENT;
|
||||
(function (REALTIME_POSTGRES_CHANGES_LISTEN_EVENT) {
|
||||
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT["ALL"] = "*";
|
||||
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT["INSERT"] = "INSERT";
|
||||
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT["UPDATE"] = "UPDATE";
|
||||
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT["DELETE"] = "DELETE";
|
||||
})(REALTIME_POSTGRES_CHANGES_LISTEN_EVENT = exports.REALTIME_POSTGRES_CHANGES_LISTEN_EVENT || (exports.REALTIME_POSTGRES_CHANGES_LISTEN_EVENT = {}));
|
||||
var REALTIME_LISTEN_TYPES;
|
||||
(function (REALTIME_LISTEN_TYPES) {
|
||||
REALTIME_LISTEN_TYPES["BROADCAST"] = "broadcast";
|
||||
REALTIME_LISTEN_TYPES["PRESENCE"] = "presence";
|
||||
REALTIME_LISTEN_TYPES["POSTGRES_CHANGES"] = "postgres_changes";
|
||||
REALTIME_LISTEN_TYPES["SYSTEM"] = "system";
|
||||
})(REALTIME_LISTEN_TYPES = exports.REALTIME_LISTEN_TYPES || (exports.REALTIME_LISTEN_TYPES = {}));
|
||||
var REALTIME_SUBSCRIBE_STATES;
|
||||
(function (REALTIME_SUBSCRIBE_STATES) {
|
||||
REALTIME_SUBSCRIBE_STATES["SUBSCRIBED"] = "SUBSCRIBED";
|
||||
REALTIME_SUBSCRIBE_STATES["TIMED_OUT"] = "TIMED_OUT";
|
||||
REALTIME_SUBSCRIBE_STATES["CLOSED"] = "CLOSED";
|
||||
REALTIME_SUBSCRIBE_STATES["CHANNEL_ERROR"] = "CHANNEL_ERROR";
|
||||
})(REALTIME_SUBSCRIBE_STATES = exports.REALTIME_SUBSCRIBE_STATES || (exports.REALTIME_SUBSCRIBE_STATES = {}));
|
||||
exports.REALTIME_CHANNEL_STATES = constants_1.CHANNEL_STATES;
|
||||
/** A channel is the basic building block of Realtime
|
||||
* and narrows the scope of data flow to subscribed clients.
|
||||
* You can think of a channel as a chatroom where participants are able to see who's online
|
||||
* and send and receive messages.
|
||||
*/
|
||||
class RealtimeChannel {
|
||||
constructor(
|
||||
/** Topic name can be any string. */
|
||||
topic, params = { config: {} }, socket) {
|
||||
this.topic = topic;
|
||||
this.params = params;
|
||||
this.socket = socket;
|
||||
this.bindings = {};
|
||||
this.state = constants_1.CHANNEL_STATES.closed;
|
||||
this.joinedOnce = false;
|
||||
this.pushBuffer = [];
|
||||
this.subTopic = topic.replace(/^realtime:/i, '');
|
||||
this.params.config = Object.assign({
|
||||
broadcast: { ack: false, self: false },
|
||||
presence: { key: '' },
|
||||
private: false,
|
||||
}, params.config);
|
||||
this.timeout = this.socket.timeout;
|
||||
this.joinPush = new push_1.default(this, constants_1.CHANNEL_EVENTS.join, this.params, this.timeout);
|
||||
this.rejoinTimer = new timer_1.default(() => this._rejoinUntilConnected(), this.socket.reconnectAfterMs);
|
||||
this.joinPush.receive('ok', () => {
|
||||
this.state = constants_1.CHANNEL_STATES.joined;
|
||||
this.rejoinTimer.reset();
|
||||
this.pushBuffer.forEach((pushEvent) => pushEvent.send());
|
||||
this.pushBuffer = [];
|
||||
});
|
||||
this._onClose(() => {
|
||||
this.rejoinTimer.reset();
|
||||
this.socket.log('channel', `close ${this.topic} ${this._joinRef()}`);
|
||||
this.state = constants_1.CHANNEL_STATES.closed;
|
||||
this.socket._remove(this);
|
||||
});
|
||||
this._onError((reason) => {
|
||||
if (this._isLeaving() || this._isClosed()) {
|
||||
return;
|
||||
}
|
||||
this.socket.log('channel', `error ${this.topic}`, reason);
|
||||
this.state = constants_1.CHANNEL_STATES.errored;
|
||||
this.rejoinTimer.scheduleTimeout();
|
||||
});
|
||||
this.joinPush.receive('timeout', () => {
|
||||
if (!this._isJoining()) {
|
||||
return;
|
||||
}
|
||||
this.socket.log('channel', `timeout ${this.topic}`, this.joinPush.timeout);
|
||||
this.state = constants_1.CHANNEL_STATES.errored;
|
||||
this.rejoinTimer.scheduleTimeout();
|
||||
});
|
||||
this._on(constants_1.CHANNEL_EVENTS.reply, {}, (payload, ref) => {
|
||||
this._trigger(this._replyEventName(ref), payload);
|
||||
});
|
||||
this.presence = new RealtimePresence_1.default(this);
|
||||
this.broadcastEndpointURL =
|
||||
(0, transformers_1.httpEndpointURL)(this.socket.endPoint) + '/api/broadcast';
|
||||
this.private = this.params.config.private || false;
|
||||
}
|
||||
/** Subscribe registers your client with the server */
|
||||
subscribe(callback, timeout = this.timeout) {
|
||||
var _a, _b;
|
||||
if (!this.socket.isConnected()) {
|
||||
this.socket.connect();
|
||||
}
|
||||
if (this.joinedOnce) {
|
||||
throw `tried to subscribe multiple times. 'subscribe' can only be called a single time per channel instance`;
|
||||
}
|
||||
else {
|
||||
const { config: { broadcast, presence, private: isPrivate }, } = this.params;
|
||||
this._onError((e) => callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR, e));
|
||||
this._onClose(() => callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.CLOSED));
|
||||
const accessTokenPayload = {};
|
||||
const config = {
|
||||
broadcast,
|
||||
presence,
|
||||
postgres_changes: (_b = (_a = this.bindings.postgres_changes) === null || _a === void 0 ? void 0 : _a.map((r) => r.filter)) !== null && _b !== void 0 ? _b : [],
|
||||
private: isPrivate,
|
||||
};
|
||||
if (this.socket.accessTokenValue) {
|
||||
accessTokenPayload.access_token = this.socket.accessTokenValue;
|
||||
}
|
||||
this.updateJoinPayload(Object.assign({ config }, accessTokenPayload));
|
||||
this.joinedOnce = true;
|
||||
this._rejoin(timeout);
|
||||
this.joinPush
|
||||
.receive('ok', async ({ postgres_changes }) => {
|
||||
var _a;
|
||||
this.socket.setAuth();
|
||||
if (postgres_changes === undefined) {
|
||||
callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.SUBSCRIBED);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
const clientPostgresBindings = this.bindings.postgres_changes;
|
||||
const bindingsLen = (_a = clientPostgresBindings === null || clientPostgresBindings === void 0 ? void 0 : clientPostgresBindings.length) !== null && _a !== void 0 ? _a : 0;
|
||||
const newPostgresBindings = [];
|
||||
for (let i = 0; i < bindingsLen; i++) {
|
||||
const clientPostgresBinding = clientPostgresBindings[i];
|
||||
const { filter: { event, schema, table, filter }, } = clientPostgresBinding;
|
||||
const serverPostgresFilter = postgres_changes && postgres_changes[i];
|
||||
if (serverPostgresFilter &&
|
||||
serverPostgresFilter.event === event &&
|
||||
serverPostgresFilter.schema === schema &&
|
||||
serverPostgresFilter.table === table &&
|
||||
serverPostgresFilter.filter === filter) {
|
||||
newPostgresBindings.push(Object.assign(Object.assign({}, clientPostgresBinding), { id: serverPostgresFilter.id }));
|
||||
}
|
||||
else {
|
||||
this.unsubscribe();
|
||||
callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR, new Error('mismatch between server and client bindings for postgres changes'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.bindings.postgres_changes = newPostgresBindings;
|
||||
callback && callback(REALTIME_SUBSCRIBE_STATES.SUBSCRIBED);
|
||||
return;
|
||||
}
|
||||
})
|
||||
.receive('error', (error) => {
|
||||
callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR, new Error(JSON.stringify(Object.values(error).join(', ') || 'error')));
|
||||
return;
|
||||
})
|
||||
.receive('timeout', () => {
|
||||
callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.TIMED_OUT);
|
||||
return;
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
presenceState() {
|
||||
return this.presence.state;
|
||||
}
|
||||
async track(payload, opts = {}) {
|
||||
return await this.send({
|
||||
type: 'presence',
|
||||
event: 'track',
|
||||
payload,
|
||||
}, opts.timeout || this.timeout);
|
||||
}
|
||||
async untrack(opts = {}) {
|
||||
return await this.send({
|
||||
type: 'presence',
|
||||
event: 'untrack',
|
||||
}, opts);
|
||||
}
|
||||
on(type, filter, callback) {
|
||||
return this._on(type, filter, callback);
|
||||
}
|
||||
/**
|
||||
* Sends a message into the channel.
|
||||
*
|
||||
* @param args Arguments to send to channel
|
||||
* @param args.type The type of event to send
|
||||
* @param args.event The name of the event being sent
|
||||
* @param args.payload Payload to be sent
|
||||
* @param opts Options to be used during the send process
|
||||
*/
|
||||
async send(args, opts = {}) {
|
||||
var _a, _b;
|
||||
if (!this._canPush() && args.type === 'broadcast') {
|
||||
const { event, payload: endpoint_payload } = args;
|
||||
const authorization = this.socket.accessTokenValue
|
||||
? `Bearer ${this.socket.accessTokenValue}`
|
||||
: '';
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: authorization,
|
||||
apikey: this.socket.apiKey ? this.socket.apiKey : '',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
messages: [
|
||||
{
|
||||
topic: this.subTopic,
|
||||
event,
|
||||
payload: endpoint_payload,
|
||||
private: this.private,
|
||||
},
|
||||
],
|
||||
}),
|
||||
};
|
||||
try {
|
||||
const response = await this._fetchWithTimeout(this.broadcastEndpointURL, options, (_a = opts.timeout) !== null && _a !== void 0 ? _a : this.timeout);
|
||||
await ((_b = response.body) === null || _b === void 0 ? void 0 : _b.cancel());
|
||||
return response.ok ? 'ok' : 'error';
|
||||
}
|
||||
catch (error) {
|
||||
if (error.name === 'AbortError') {
|
||||
return 'timed out';
|
||||
}
|
||||
else {
|
||||
return 'error';
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return new Promise((resolve) => {
|
||||
var _a, _b, _c;
|
||||
const push = this._push(args.type, args, opts.timeout || this.timeout);
|
||||
if (args.type === 'broadcast' && !((_c = (_b = (_a = this.params) === null || _a === void 0 ? void 0 : _a.config) === null || _b === void 0 ? void 0 : _b.broadcast) === null || _c === void 0 ? void 0 : _c.ack)) {
|
||||
resolve('ok');
|
||||
}
|
||||
push.receive('ok', () => resolve('ok'));
|
||||
push.receive('error', () => resolve('error'));
|
||||
push.receive('timeout', () => resolve('timed out'));
|
||||
});
|
||||
}
|
||||
}
|
||||
updateJoinPayload(payload) {
|
||||
this.joinPush.updatePayload(payload);
|
||||
}
|
||||
/**
|
||||
* Leaves the channel.
|
||||
*
|
||||
* Unsubscribes from server events, and instructs channel to terminate on server.
|
||||
* Triggers onClose() hooks.
|
||||
*
|
||||
* To receive leave acknowledgements, use the a `receive` hook to bind to the server ack, ie:
|
||||
* channel.unsubscribe().receive("ok", () => alert("left!") )
|
||||
*/
|
||||
unsubscribe(timeout = this.timeout) {
|
||||
this.state = constants_1.CHANNEL_STATES.leaving;
|
||||
const onClose = () => {
|
||||
this.socket.log('channel', `leave ${this.topic}`);
|
||||
this._trigger(constants_1.CHANNEL_EVENTS.close, 'leave', this._joinRef());
|
||||
};
|
||||
this.rejoinTimer.reset();
|
||||
// Destroy joinPush to avoid connection timeouts during unscription phase
|
||||
this.joinPush.destroy();
|
||||
return new Promise((resolve) => {
|
||||
const leavePush = new push_1.default(this, constants_1.CHANNEL_EVENTS.leave, {}, timeout);
|
||||
leavePush
|
||||
.receive('ok', () => {
|
||||
onClose();
|
||||
resolve('ok');
|
||||
})
|
||||
.receive('timeout', () => {
|
||||
onClose();
|
||||
resolve('timed out');
|
||||
})
|
||||
.receive('error', () => {
|
||||
resolve('error');
|
||||
});
|
||||
leavePush.send();
|
||||
if (!this._canPush()) {
|
||||
leavePush.trigger('ok', {});
|
||||
}
|
||||
});
|
||||
}
|
||||
/** @internal */
|
||||
async _fetchWithTimeout(url, options, timeout) {
|
||||
const controller = new AbortController();
|
||||
const id = setTimeout(() => controller.abort(), timeout);
|
||||
const response = await this.socket.fetch(url, Object.assign(Object.assign({}, options), { signal: controller.signal }));
|
||||
clearTimeout(id);
|
||||
return response;
|
||||
}
|
||||
/** @internal */
|
||||
_push(event, payload, timeout = this.timeout) {
|
||||
if (!this.joinedOnce) {
|
||||
throw `tried to push '${event}' to '${this.topic}' before joining. Use channel.subscribe() before pushing events`;
|
||||
}
|
||||
let pushEvent = new push_1.default(this, event, payload, timeout);
|
||||
if (this._canPush()) {
|
||||
pushEvent.send();
|
||||
}
|
||||
else {
|
||||
pushEvent.startTimeout();
|
||||
this.pushBuffer.push(pushEvent);
|
||||
}
|
||||
return pushEvent;
|
||||
}
|
||||
/**
|
||||
* Overridable message hook
|
||||
*
|
||||
* Receives all events for specialized message handling before dispatching to the channel callbacks.
|
||||
* Must return the payload, modified or unmodified.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_onMessage(_event, payload, _ref) {
|
||||
return payload;
|
||||
}
|
||||
/** @internal */
|
||||
_isMember(topic) {
|
||||
return this.topic === topic;
|
||||
}
|
||||
/** @internal */
|
||||
_joinRef() {
|
||||
return this.joinPush.ref;
|
||||
}
|
||||
/** @internal */
|
||||
_trigger(type, payload, ref) {
|
||||
var _a, _b;
|
||||
const typeLower = type.toLocaleLowerCase();
|
||||
const { close, error, leave, join } = constants_1.CHANNEL_EVENTS;
|
||||
const events = [close, error, leave, join];
|
||||
if (ref && events.indexOf(typeLower) >= 0 && ref !== this._joinRef()) {
|
||||
return;
|
||||
}
|
||||
let handledPayload = this._onMessage(typeLower, payload, ref);
|
||||
if (payload && !handledPayload) {
|
||||
throw 'channel onMessage callbacks must return the payload, modified or unmodified';
|
||||
}
|
||||
if (['insert', 'update', 'delete'].includes(typeLower)) {
|
||||
(_a = this.bindings.postgres_changes) === null || _a === void 0 ? void 0 : _a.filter((bind) => {
|
||||
var _a, _b, _c;
|
||||
return (((_a = bind.filter) === null || _a === void 0 ? void 0 : _a.event) === '*' ||
|
||||
((_c = (_b = bind.filter) === null || _b === void 0 ? void 0 : _b.event) === null || _c === void 0 ? void 0 : _c.toLocaleLowerCase()) === typeLower);
|
||||
}).map((bind) => bind.callback(handledPayload, ref));
|
||||
}
|
||||
else {
|
||||
(_b = this.bindings[typeLower]) === null || _b === void 0 ? void 0 : _b.filter((bind) => {
|
||||
var _a, _b, _c, _d, _e, _f;
|
||||
if (['broadcast', 'presence', 'postgres_changes'].includes(typeLower)) {
|
||||
if ('id' in bind) {
|
||||
const bindId = bind.id;
|
||||
const bindEvent = (_a = bind.filter) === null || _a === void 0 ? void 0 : _a.event;
|
||||
return (bindId &&
|
||||
((_b = payload.ids) === null || _b === void 0 ? void 0 : _b.includes(bindId)) &&
|
||||
(bindEvent === '*' ||
|
||||
(bindEvent === null || bindEvent === void 0 ? void 0 : bindEvent.toLocaleLowerCase()) ===
|
||||
((_c = payload.data) === null || _c === void 0 ? void 0 : _c.type.toLocaleLowerCase())));
|
||||
}
|
||||
else {
|
||||
const bindEvent = (_e = (_d = bind === null || bind === void 0 ? void 0 : bind.filter) === null || _d === void 0 ? void 0 : _d.event) === null || _e === void 0 ? void 0 : _e.toLocaleLowerCase();
|
||||
return (bindEvent === '*' ||
|
||||
bindEvent === ((_f = payload === null || payload === void 0 ? void 0 : payload.event) === null || _f === void 0 ? void 0 : _f.toLocaleLowerCase()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
return bind.type.toLocaleLowerCase() === typeLower;
|
||||
}
|
||||
}).map((bind) => {
|
||||
if (typeof handledPayload === 'object' && 'ids' in handledPayload) {
|
||||
const postgresChanges = handledPayload.data;
|
||||
const { schema, table, commit_timestamp, type, errors } = postgresChanges;
|
||||
const enrichedPayload = {
|
||||
schema: schema,
|
||||
table: table,
|
||||
commit_timestamp: commit_timestamp,
|
||||
eventType: type,
|
||||
new: {},
|
||||
old: {},
|
||||
errors: errors,
|
||||
};
|
||||
handledPayload = Object.assign(Object.assign({}, enrichedPayload), this._getPayloadRecords(postgresChanges));
|
||||
}
|
||||
bind.callback(handledPayload, ref);
|
||||
});
|
||||
}
|
||||
}
|
||||
/** @internal */
|
||||
_isClosed() {
|
||||
return this.state === constants_1.CHANNEL_STATES.closed;
|
||||
}
|
||||
/** @internal */
|
||||
_isJoined() {
|
||||
return this.state === constants_1.CHANNEL_STATES.joined;
|
||||
}
|
||||
/** @internal */
|
||||
_isJoining() {
|
||||
return this.state === constants_1.CHANNEL_STATES.joining;
|
||||
}
|
||||
/** @internal */
|
||||
_isLeaving() {
|
||||
return this.state === constants_1.CHANNEL_STATES.leaving;
|
||||
}
|
||||
/** @internal */
|
||||
_replyEventName(ref) {
|
||||
return `chan_reply_${ref}`;
|
||||
}
|
||||
/** @internal */
|
||||
_on(type, filter, callback) {
|
||||
const typeLower = type.toLocaleLowerCase();
|
||||
const binding = {
|
||||
type: typeLower,
|
||||
filter: filter,
|
||||
callback: callback,
|
||||
};
|
||||
if (this.bindings[typeLower]) {
|
||||
this.bindings[typeLower].push(binding);
|
||||
}
|
||||
else {
|
||||
this.bindings[typeLower] = [binding];
|
||||
}
|
||||
return this;
|
||||
}
|
||||
/** @internal */
|
||||
_off(type, filter) {
|
||||
const typeLower = type.toLocaleLowerCase();
|
||||
this.bindings[typeLower] = this.bindings[typeLower].filter((bind) => {
|
||||
var _a;
|
||||
return !(((_a = bind.type) === null || _a === void 0 ? void 0 : _a.toLocaleLowerCase()) === typeLower &&
|
||||
RealtimeChannel.isEqual(bind.filter, filter));
|
||||
});
|
||||
return this;
|
||||
}
|
||||
/** @internal */
|
||||
static isEqual(obj1, obj2) {
|
||||
if (Object.keys(obj1).length !== Object.keys(obj2).length) {
|
||||
return false;
|
||||
}
|
||||
for (const k in obj1) {
|
||||
if (obj1[k] !== obj2[k]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/** @internal */
|
||||
_rejoinUntilConnected() {
|
||||
this.rejoinTimer.scheduleTimeout();
|
||||
if (this.socket.isConnected()) {
|
||||
this._rejoin();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Registers a callback that will be executed when the channel closes.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_onClose(callback) {
|
||||
this._on(constants_1.CHANNEL_EVENTS.close, {}, callback);
|
||||
}
|
||||
/**
|
||||
* Registers a callback that will be executed when the channel encounteres an error.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_onError(callback) {
|
||||
this._on(constants_1.CHANNEL_EVENTS.error, {}, (reason) => callback(reason));
|
||||
}
|
||||
/**
|
||||
* Returns `true` if the socket is connected and the channel has been joined.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_canPush() {
|
||||
return this.socket.isConnected() && this._isJoined();
|
||||
}
|
||||
/** @internal */
|
||||
_rejoin(timeout = this.timeout) {
|
||||
if (this._isLeaving()) {
|
||||
return;
|
||||
}
|
||||
this.socket._leaveOpenTopic(this.topic);
|
||||
this.state = constants_1.CHANNEL_STATES.joining;
|
||||
this.joinPush.resend(timeout);
|
||||
}
|
||||
/** @internal */
|
||||
_getPayloadRecords(payload) {
|
||||
const records = {
|
||||
new: {},
|
||||
old: {},
|
||||
};
|
||||
if (payload.type === 'INSERT' || payload.type === 'UPDATE') {
|
||||
records.new = Transformers.convertChangeData(payload.columns, payload.record);
|
||||
}
|
||||
if (payload.type === 'UPDATE' || payload.type === 'DELETE') {
|
||||
records.old = Transformers.convertChangeData(payload.columns, payload.old_record);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
}
|
||||
exports.default = RealtimeChannel;
|
||||
//# sourceMappingURL=RealtimeChannel.js.map
|
||||
+514
@@ -0,0 +1,514 @@
|
||||
"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 (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__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 constants_1 = require("./lib/constants");
|
||||
const serializer_1 = __importDefault(require("./lib/serializer"));
|
||||
const timer_1 = __importDefault(require("./lib/timer"));
|
||||
const transformers_1 = require("./lib/transformers");
|
||||
const RealtimeChannel_1 = __importDefault(require("./RealtimeChannel"));
|
||||
const noop = () => { };
|
||||
const NATIVE_WEBSOCKET_AVAILABLE = typeof WebSocket !== 'undefined';
|
||||
const WORKER_SCRIPT = `
|
||||
addEventListener("message", (e) => {
|
||||
if (e.data.event === "start") {
|
||||
setInterval(() => postMessage({ event: "keepAlive" }), e.data.interval);
|
||||
}
|
||||
});`;
|
||||
class RealtimeClient {
|
||||
/**
|
||||
* Initializes the Socket.
|
||||
*
|
||||
* @param endPoint The string WebSocket endpoint, ie, "ws://example.com/socket", "wss://example.com", "/socket" (inherited host & protocol)
|
||||
* @param httpEndpoint The string HTTP endpoint, ie, "https://example.com", "/" (inherited host & protocol)
|
||||
* @param options.transport The Websocket Transport, for example WebSocket.
|
||||
* @param options.timeout The default timeout in milliseconds to trigger push timeouts.
|
||||
* @param options.params The optional params to pass when connecting.
|
||||
* @param options.headers The optional headers to pass when connecting.
|
||||
* @param options.heartbeatIntervalMs The millisec interval to send a heartbeat message.
|
||||
* @param options.logger The optional function for specialized logging, ie: logger: (kind, msg, data) => { console.log(`${kind}: ${msg}`, data) }
|
||||
* @param options.encode The function to encode outgoing messages. Defaults to JSON: (payload, callback) => callback(JSON.stringify(payload))
|
||||
* @param options.decode The function to decode incoming messages. Defaults to Serializer's decode.
|
||||
* @param options.reconnectAfterMs he optional function that returns the millsec reconnect interval. Defaults to stepped backoff off.
|
||||
* @param options.worker Use Web Worker to set a side flow. Defaults to false.
|
||||
* @param options.workerUrl The URL of the worker script. Defaults to https://realtime.supabase.com/worker.js that includes a heartbeat event call to keep the connection alive.
|
||||
*/
|
||||
constructor(endPoint, options) {
|
||||
var _a;
|
||||
this.accessTokenValue = null;
|
||||
this.apiKey = null;
|
||||
this.channels = [];
|
||||
this.endPoint = '';
|
||||
this.httpEndpoint = '';
|
||||
this.headers = constants_1.DEFAULT_HEADERS;
|
||||
this.params = {};
|
||||
this.timeout = constants_1.DEFAULT_TIMEOUT;
|
||||
this.heartbeatIntervalMs = 30000;
|
||||
this.heartbeatTimer = undefined;
|
||||
this.pendingHeartbeatRef = null;
|
||||
this.ref = 0;
|
||||
this.logger = noop;
|
||||
this.conn = null;
|
||||
this.sendBuffer = [];
|
||||
this.serializer = new serializer_1.default();
|
||||
this.stateChangeCallbacks = {
|
||||
open: [],
|
||||
close: [],
|
||||
error: [],
|
||||
message: [],
|
||||
};
|
||||
this.accessToken = null;
|
||||
/**
|
||||
* Use either custom fetch, if provided, or default fetch to make HTTP requests
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
this._resolveFetch = (customFetch) => {
|
||||
let _fetch;
|
||||
if (customFetch) {
|
||||
_fetch = customFetch;
|
||||
}
|
||||
else if (typeof fetch === 'undefined') {
|
||||
_fetch = (...args) => Promise.resolve().then(() => __importStar(require('@supabase/node-fetch'))).then(({ default: fetch }) => fetch(...args));
|
||||
}
|
||||
else {
|
||||
_fetch = fetch;
|
||||
}
|
||||
return (...args) => _fetch(...args);
|
||||
};
|
||||
this.endPoint = `${endPoint}/${constants_1.TRANSPORTS.websocket}`;
|
||||
this.httpEndpoint = (0, transformers_1.httpEndpointURL)(endPoint);
|
||||
if (options === null || options === void 0 ? void 0 : options.transport) {
|
||||
this.transport = options.transport;
|
||||
}
|
||||
else {
|
||||
this.transport = null;
|
||||
}
|
||||
if (options === null || options === void 0 ? void 0 : options.params)
|
||||
this.params = options.params;
|
||||
if (options === null || options === void 0 ? void 0 : options.headers)
|
||||
this.headers = Object.assign(Object.assign({}, this.headers), options.headers);
|
||||
if (options === null || options === void 0 ? void 0 : options.timeout)
|
||||
this.timeout = options.timeout;
|
||||
if (options === null || options === void 0 ? void 0 : options.logger)
|
||||
this.logger = options.logger;
|
||||
if (options === null || options === void 0 ? void 0 : options.heartbeatIntervalMs)
|
||||
this.heartbeatIntervalMs = options.heartbeatIntervalMs;
|
||||
const accessTokenValue = (_a = options === null || options === void 0 ? void 0 : options.params) === null || _a === void 0 ? void 0 : _a.apikey;
|
||||
if (accessTokenValue) {
|
||||
this.accessTokenValue = accessTokenValue;
|
||||
this.apiKey = accessTokenValue;
|
||||
}
|
||||
this.reconnectAfterMs = (options === null || options === void 0 ? void 0 : options.reconnectAfterMs)
|
||||
? options.reconnectAfterMs
|
||||
: (tries) => {
|
||||
return [1000, 2000, 5000, 10000][tries - 1] || 10000;
|
||||
};
|
||||
this.encode = (options === null || options === void 0 ? void 0 : options.encode)
|
||||
? options.encode
|
||||
: (payload, callback) => {
|
||||
return callback(JSON.stringify(payload));
|
||||
};
|
||||
this.decode = (options === null || options === void 0 ? void 0 : options.decode)
|
||||
? options.decode
|
||||
: this.serializer.decode.bind(this.serializer);
|
||||
this.reconnectTimer = new timer_1.default(async () => {
|
||||
this.disconnect();
|
||||
this.connect();
|
||||
}, this.reconnectAfterMs);
|
||||
this.fetch = this._resolveFetch(options === null || options === void 0 ? void 0 : options.fetch);
|
||||
if (options === null || options === void 0 ? void 0 : options.worker) {
|
||||
if (typeof window !== 'undefined' && !window.Worker) {
|
||||
throw new Error('Web Worker is not supported');
|
||||
}
|
||||
this.worker = (options === null || options === void 0 ? void 0 : options.worker) || false;
|
||||
this.workerUrl = options === null || options === void 0 ? void 0 : options.workerUrl;
|
||||
}
|
||||
this.accessToken = (options === null || options === void 0 ? void 0 : options.accessToken) || null;
|
||||
}
|
||||
/**
|
||||
* Connects the socket, unless already connected.
|
||||
*/
|
||||
connect() {
|
||||
if (this.conn) {
|
||||
return;
|
||||
}
|
||||
if (this.transport) {
|
||||
this.conn = new this.transport(this.endpointURL(), undefined, {
|
||||
headers: this.headers,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (NATIVE_WEBSOCKET_AVAILABLE) {
|
||||
this.conn = new WebSocket(this.endpointURL());
|
||||
this.setupConnection();
|
||||
return;
|
||||
}
|
||||
this.conn = new WSWebSocketDummy(this.endpointURL(), undefined, {
|
||||
close: () => {
|
||||
this.conn = null;
|
||||
},
|
||||
});
|
||||
Promise.resolve().then(() => __importStar(require('ws'))).then(({ default: WS }) => {
|
||||
this.conn = new WS(this.endpointURL(), undefined, {
|
||||
headers: this.headers,
|
||||
});
|
||||
this.setupConnection();
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Returns the URL of the websocket.
|
||||
* @returns string The URL of the websocket.
|
||||
*/
|
||||
endpointURL() {
|
||||
return this._appendParams(this.endPoint, Object.assign({}, this.params, { vsn: constants_1.VSN }));
|
||||
}
|
||||
/**
|
||||
* Disconnects the socket.
|
||||
*
|
||||
* @param code A numeric status code to send on disconnect.
|
||||
* @param reason A custom reason for the disconnect.
|
||||
*/
|
||||
disconnect(code, reason) {
|
||||
if (this.conn) {
|
||||
this.conn.onclose = function () { }; // noop
|
||||
if (code) {
|
||||
this.conn.close(code, reason !== null && reason !== void 0 ? reason : '');
|
||||
}
|
||||
else {
|
||||
this.conn.close();
|
||||
}
|
||||
this.conn = null;
|
||||
// remove open handles
|
||||
this.heartbeatTimer && clearInterval(this.heartbeatTimer);
|
||||
this.reconnectTimer.reset();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns all created channels
|
||||
*/
|
||||
getChannels() {
|
||||
return this.channels;
|
||||
}
|
||||
/**
|
||||
* Unsubscribes and removes a single channel
|
||||
* @param channel A RealtimeChannel instance
|
||||
*/
|
||||
async removeChannel(channel) {
|
||||
const status = await channel.unsubscribe();
|
||||
if (this.channels.length === 0) {
|
||||
this.disconnect();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
/**
|
||||
* Unsubscribes and removes all channels
|
||||
*/
|
||||
async removeAllChannels() {
|
||||
const values_1 = await Promise.all(this.channels.map((channel) => channel.unsubscribe()));
|
||||
this.disconnect();
|
||||
return values_1;
|
||||
}
|
||||
/**
|
||||
* Logs the message.
|
||||
*
|
||||
* For customized logging, `this.logger` can be overridden.
|
||||
*/
|
||||
log(kind, msg, data) {
|
||||
this.logger(kind, msg, data);
|
||||
}
|
||||
/**
|
||||
* Returns the current state of the socket.
|
||||
*/
|
||||
connectionState() {
|
||||
switch (this.conn && this.conn.readyState) {
|
||||
case constants_1.SOCKET_STATES.connecting:
|
||||
return constants_1.CONNECTION_STATE.Connecting;
|
||||
case constants_1.SOCKET_STATES.open:
|
||||
return constants_1.CONNECTION_STATE.Open;
|
||||
case constants_1.SOCKET_STATES.closing:
|
||||
return constants_1.CONNECTION_STATE.Closing;
|
||||
default:
|
||||
return constants_1.CONNECTION_STATE.Closed;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns `true` is the connection is open.
|
||||
*/
|
||||
isConnected() {
|
||||
return this.connectionState() === constants_1.CONNECTION_STATE.Open;
|
||||
}
|
||||
channel(topic, params = { config: {} }) {
|
||||
const chan = new RealtimeChannel_1.default(`realtime:${topic}`, params, this);
|
||||
this.channels.push(chan);
|
||||
return chan;
|
||||
}
|
||||
/**
|
||||
* Push out a message if the socket is connected.
|
||||
*
|
||||
* If the socket is not connected, the message gets enqueued within a local buffer, and sent out when a connection is next established.
|
||||
*/
|
||||
push(data) {
|
||||
const { topic, event, payload, ref } = data;
|
||||
const callback = () => {
|
||||
this.encode(data, (result) => {
|
||||
var _a;
|
||||
(_a = this.conn) === null || _a === void 0 ? void 0 : _a.send(result);
|
||||
});
|
||||
};
|
||||
this.log('push', `${topic} ${event} (${ref})`, payload);
|
||||
if (this.isConnected()) {
|
||||
callback();
|
||||
}
|
||||
else {
|
||||
this.sendBuffer.push(callback);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Sets the JWT access token used for channel subscription authorization and Realtime RLS.
|
||||
*
|
||||
* If param is null it will use the `accessToken` callback function or the token set on the client.
|
||||
*
|
||||
* On callback used, it will set the value of the token internal to the client.
|
||||
*
|
||||
* @param token A JWT string to override the token set on the client.
|
||||
*/
|
||||
async setAuth(token = null) {
|
||||
let tokenToSend = token ||
|
||||
(this.accessToken && (await this.accessToken())) ||
|
||||
this.accessTokenValue;
|
||||
if (tokenToSend) {
|
||||
let parsed = null;
|
||||
try {
|
||||
parsed = JSON.parse(atob(tokenToSend.split('.')[1]));
|
||||
}
|
||||
catch (_error) { }
|
||||
if (parsed && parsed.exp) {
|
||||
let now = Math.floor(Date.now() / 1000);
|
||||
let valid = now - parsed.exp < 0;
|
||||
if (!valid) {
|
||||
this.log('auth', `InvalidJWTToken: Invalid value for JWT claim "exp" with value ${parsed.exp}`);
|
||||
return Promise.reject(`InvalidJWTToken: Invalid value for JWT claim "exp" with value ${parsed.exp}`);
|
||||
}
|
||||
}
|
||||
this.accessTokenValue = tokenToSend;
|
||||
this.channels.forEach((channel) => {
|
||||
tokenToSend && channel.updateJoinPayload({ access_token: tokenToSend });
|
||||
if (channel.joinedOnce && channel._isJoined()) {
|
||||
channel._push(constants_1.CHANNEL_EVENTS.access_token, {
|
||||
access_token: tokenToSend,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Sends a heartbeat message if the socket is connected.
|
||||
*/
|
||||
async sendHeartbeat() {
|
||||
var _a;
|
||||
if (!this.isConnected()) {
|
||||
return;
|
||||
}
|
||||
if (this.pendingHeartbeatRef) {
|
||||
this.pendingHeartbeatRef = null;
|
||||
this.log('transport', 'heartbeat timeout. Attempting to re-establish connection');
|
||||
(_a = this.conn) === null || _a === void 0 ? void 0 : _a.close(constants_1.WS_CLOSE_NORMAL, 'hearbeat timeout');
|
||||
return;
|
||||
}
|
||||
this.pendingHeartbeatRef = this._makeRef();
|
||||
this.push({
|
||||
topic: 'phoenix',
|
||||
event: 'heartbeat',
|
||||
payload: {},
|
||||
ref: this.pendingHeartbeatRef,
|
||||
});
|
||||
this.setAuth();
|
||||
}
|
||||
/**
|
||||
* Flushes send buffer
|
||||
*/
|
||||
flushSendBuffer() {
|
||||
if (this.isConnected() && this.sendBuffer.length > 0) {
|
||||
this.sendBuffer.forEach((callback) => callback());
|
||||
this.sendBuffer = [];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Return the next message ref, accounting for overflows
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_makeRef() {
|
||||
let newRef = this.ref + 1;
|
||||
if (newRef === this.ref) {
|
||||
this.ref = 0;
|
||||
}
|
||||
else {
|
||||
this.ref = newRef;
|
||||
}
|
||||
return this.ref.toString();
|
||||
}
|
||||
/**
|
||||
* Unsubscribe from channels with the specified topic.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_leaveOpenTopic(topic) {
|
||||
let dupChannel = this.channels.find((c) => c.topic === topic && (c._isJoined() || c._isJoining()));
|
||||
if (dupChannel) {
|
||||
this.log('transport', `leaving duplicate topic "${topic}"`);
|
||||
dupChannel.unsubscribe();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Removes a subscription from the socket.
|
||||
*
|
||||
* @param channel An open subscription.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_remove(channel) {
|
||||
this.channels = this.channels.filter((c) => c._joinRef() !== channel._joinRef());
|
||||
}
|
||||
/**
|
||||
* Sets up connection handlers.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
setupConnection() {
|
||||
if (this.conn) {
|
||||
this.conn.binaryType = 'arraybuffer';
|
||||
this.conn.onopen = () => this._onConnOpen();
|
||||
this.conn.onerror = (error) => this._onConnError(error);
|
||||
this.conn.onmessage = (event) => this._onConnMessage(event);
|
||||
this.conn.onclose = (event) => this._onConnClose(event);
|
||||
}
|
||||
}
|
||||
/** @internal */
|
||||
_onConnMessage(rawMessage) {
|
||||
this.decode(rawMessage.data, (msg) => {
|
||||
let { topic, event, payload, ref } = msg;
|
||||
if (ref && ref === this.pendingHeartbeatRef) {
|
||||
this.pendingHeartbeatRef = null;
|
||||
}
|
||||
this.log('receive', `${payload.status || ''} ${topic} ${event} ${(ref && '(' + ref + ')') || ''}`, payload);
|
||||
this.channels
|
||||
.filter((channel) => channel._isMember(topic))
|
||||
.forEach((channel) => channel._trigger(event, payload, ref));
|
||||
this.stateChangeCallbacks.message.forEach((callback) => callback(msg));
|
||||
});
|
||||
}
|
||||
/** @internal */
|
||||
async _onConnOpen() {
|
||||
this.log('transport', `connected to ${this.endpointURL()}`);
|
||||
this.flushSendBuffer();
|
||||
this.reconnectTimer.reset();
|
||||
if (!this.worker) {
|
||||
this.heartbeatTimer && clearInterval(this.heartbeatTimer);
|
||||
this.heartbeatTimer = setInterval(() => this.sendHeartbeat(), this.heartbeatIntervalMs);
|
||||
}
|
||||
else {
|
||||
if (this.workerUrl) {
|
||||
this.log('worker', `starting worker for from ${this.workerUrl}`);
|
||||
}
|
||||
else {
|
||||
this.log('worker', `starting default worker`);
|
||||
}
|
||||
const objectUrl = this._workerObjectUrl(this.workerUrl);
|
||||
this.workerRef = new Worker(objectUrl);
|
||||
this.workerRef.onerror = (error) => {
|
||||
this.log('worker', 'worker error', error.message);
|
||||
this.workerRef.terminate();
|
||||
};
|
||||
this.workerRef.onmessage = (event) => {
|
||||
if (event.data.event === 'keepAlive') {
|
||||
this.sendHeartbeat();
|
||||
}
|
||||
};
|
||||
this.workerRef.postMessage({
|
||||
event: 'start',
|
||||
interval: this.heartbeatIntervalMs,
|
||||
});
|
||||
}
|
||||
this.stateChangeCallbacks.open.forEach((callback) => callback());
|
||||
}
|
||||
/** @internal */
|
||||
_onConnClose(event) {
|
||||
this.log('transport', 'close', event);
|
||||
this._triggerChanError();
|
||||
this.heartbeatTimer && clearInterval(this.heartbeatTimer);
|
||||
this.reconnectTimer.scheduleTimeout();
|
||||
this.stateChangeCallbacks.close.forEach((callback) => callback(event));
|
||||
}
|
||||
/** @internal */
|
||||
_onConnError(error) {
|
||||
this.log('transport', error.message);
|
||||
this._triggerChanError();
|
||||
this.stateChangeCallbacks.error.forEach((callback) => callback(error));
|
||||
}
|
||||
/** @internal */
|
||||
_triggerChanError() {
|
||||
this.channels.forEach((channel) => channel._trigger(constants_1.CHANNEL_EVENTS.error));
|
||||
}
|
||||
/** @internal */
|
||||
_appendParams(url, params) {
|
||||
if (Object.keys(params).length === 0) {
|
||||
return url;
|
||||
}
|
||||
const prefix = url.match(/\?/) ? '&' : '?';
|
||||
const query = new URLSearchParams(params);
|
||||
return `${url}${prefix}${query}`;
|
||||
}
|
||||
_workerObjectUrl(url) {
|
||||
let result_url;
|
||||
if (url) {
|
||||
result_url = url;
|
||||
}
|
||||
else {
|
||||
const blob = new Blob([WORKER_SCRIPT], { type: 'application/javascript' });
|
||||
result_url = URL.createObjectURL(blob);
|
||||
}
|
||||
return result_url;
|
||||
}
|
||||
}
|
||||
exports.default = RealtimeClient;
|
||||
class WSWebSocketDummy {
|
||||
constructor(address, _protocols, options) {
|
||||
this.binaryType = 'arraybuffer';
|
||||
this.onclose = () => { };
|
||||
this.onerror = () => { };
|
||||
this.onmessage = () => { };
|
||||
this.onopen = () => { };
|
||||
this.readyState = constants_1.SOCKET_STATES.connecting;
|
||||
this.send = () => { };
|
||||
this.url = null;
|
||||
this.url = address;
|
||||
this.close = options.close;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=RealtimeClient.js.map
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
"use strict";
|
||||
/*
|
||||
This file draws heavily from https://github.com/phoenixframework/phoenix/blob/d344ec0a732ab4ee204215b31de69cf4be72e3bf/assets/js/phoenix/presence.js
|
||||
License: https://github.com/phoenixframework/phoenix/blob/d344ec0a732ab4ee204215b31de69cf4be72e3bf/LICENSE.md
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.REALTIME_PRESENCE_LISTEN_EVENTS = void 0;
|
||||
var REALTIME_PRESENCE_LISTEN_EVENTS;
|
||||
(function (REALTIME_PRESENCE_LISTEN_EVENTS) {
|
||||
REALTIME_PRESENCE_LISTEN_EVENTS["SYNC"] = "sync";
|
||||
REALTIME_PRESENCE_LISTEN_EVENTS["JOIN"] = "join";
|
||||
REALTIME_PRESENCE_LISTEN_EVENTS["LEAVE"] = "leave";
|
||||
})(REALTIME_PRESENCE_LISTEN_EVENTS = exports.REALTIME_PRESENCE_LISTEN_EVENTS || (exports.REALTIME_PRESENCE_LISTEN_EVENTS = {}));
|
||||
class RealtimePresence {
|
||||
/**
|
||||
* Initializes the Presence.
|
||||
*
|
||||
* @param channel - The RealtimeChannel
|
||||
* @param opts - The options,
|
||||
* for example `{events: {state: 'state', diff: 'diff'}}`
|
||||
*/
|
||||
constructor(channel, opts) {
|
||||
this.channel = channel;
|
||||
this.state = {};
|
||||
this.pendingDiffs = [];
|
||||
this.joinRef = null;
|
||||
this.caller = {
|
||||
onJoin: () => { },
|
||||
onLeave: () => { },
|
||||
onSync: () => { },
|
||||
};
|
||||
const events = (opts === null || opts === void 0 ? void 0 : opts.events) || {
|
||||
state: 'presence_state',
|
||||
diff: 'presence_diff',
|
||||
};
|
||||
this.channel._on(events.state, {}, (newState) => {
|
||||
const { onJoin, onLeave, onSync } = this.caller;
|
||||
this.joinRef = this.channel._joinRef();
|
||||
this.state = RealtimePresence.syncState(this.state, newState, onJoin, onLeave);
|
||||
this.pendingDiffs.forEach((diff) => {
|
||||
this.state = RealtimePresence.syncDiff(this.state, diff, onJoin, onLeave);
|
||||
});
|
||||
this.pendingDiffs = [];
|
||||
onSync();
|
||||
});
|
||||
this.channel._on(events.diff, {}, (diff) => {
|
||||
const { onJoin, onLeave, onSync } = this.caller;
|
||||
if (this.inPendingSyncState()) {
|
||||
this.pendingDiffs.push(diff);
|
||||
}
|
||||
else {
|
||||
this.state = RealtimePresence.syncDiff(this.state, diff, onJoin, onLeave);
|
||||
onSync();
|
||||
}
|
||||
});
|
||||
this.onJoin((key, currentPresences, newPresences) => {
|
||||
this.channel._trigger('presence', {
|
||||
event: 'join',
|
||||
key,
|
||||
currentPresences,
|
||||
newPresences,
|
||||
});
|
||||
});
|
||||
this.onLeave((key, currentPresences, leftPresences) => {
|
||||
this.channel._trigger('presence', {
|
||||
event: 'leave',
|
||||
key,
|
||||
currentPresences,
|
||||
leftPresences,
|
||||
});
|
||||
});
|
||||
this.onSync(() => {
|
||||
this.channel._trigger('presence', { event: 'sync' });
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Used to sync the list of presences on the server with the
|
||||
* client's state.
|
||||
*
|
||||
* An optional `onJoin` and `onLeave` callback can be provided to
|
||||
* react to changes in the client's local presences across
|
||||
* disconnects and reconnects with the server.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static syncState(currentState, newState, onJoin, onLeave) {
|
||||
const state = this.cloneDeep(currentState);
|
||||
const transformedState = this.transformState(newState);
|
||||
const joins = {};
|
||||
const leaves = {};
|
||||
this.map(state, (key, presences) => {
|
||||
if (!transformedState[key]) {
|
||||
leaves[key] = presences;
|
||||
}
|
||||
});
|
||||
this.map(transformedState, (key, newPresences) => {
|
||||
const currentPresences = state[key];
|
||||
if (currentPresences) {
|
||||
const newPresenceRefs = newPresences.map((m) => m.presence_ref);
|
||||
const curPresenceRefs = currentPresences.map((m) => m.presence_ref);
|
||||
const joinedPresences = newPresences.filter((m) => curPresenceRefs.indexOf(m.presence_ref) < 0);
|
||||
const leftPresences = currentPresences.filter((m) => newPresenceRefs.indexOf(m.presence_ref) < 0);
|
||||
if (joinedPresences.length > 0) {
|
||||
joins[key] = joinedPresences;
|
||||
}
|
||||
if (leftPresences.length > 0) {
|
||||
leaves[key] = leftPresences;
|
||||
}
|
||||
}
|
||||
else {
|
||||
joins[key] = newPresences;
|
||||
}
|
||||
});
|
||||
return this.syncDiff(state, { joins, leaves }, onJoin, onLeave);
|
||||
}
|
||||
/**
|
||||
* Used to sync a diff of presence join and leave events from the
|
||||
* server, as they happen.
|
||||
*
|
||||
* Like `syncState`, `syncDiff` accepts optional `onJoin` and
|
||||
* `onLeave` callbacks to react to a user joining or leaving from a
|
||||
* device.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static syncDiff(state, diff, onJoin, onLeave) {
|
||||
const { joins, leaves } = {
|
||||
joins: this.transformState(diff.joins),
|
||||
leaves: this.transformState(diff.leaves),
|
||||
};
|
||||
if (!onJoin) {
|
||||
onJoin = () => { };
|
||||
}
|
||||
if (!onLeave) {
|
||||
onLeave = () => { };
|
||||
}
|
||||
this.map(joins, (key, newPresences) => {
|
||||
var _a;
|
||||
const currentPresences = (_a = state[key]) !== null && _a !== void 0 ? _a : [];
|
||||
state[key] = this.cloneDeep(newPresences);
|
||||
if (currentPresences.length > 0) {
|
||||
const joinedPresenceRefs = state[key].map((m) => m.presence_ref);
|
||||
const curPresences = currentPresences.filter((m) => joinedPresenceRefs.indexOf(m.presence_ref) < 0);
|
||||
state[key].unshift(...curPresences);
|
||||
}
|
||||
onJoin(key, currentPresences, newPresences);
|
||||
});
|
||||
this.map(leaves, (key, leftPresences) => {
|
||||
let currentPresences = state[key];
|
||||
if (!currentPresences)
|
||||
return;
|
||||
const presenceRefsToRemove = leftPresences.map((m) => m.presence_ref);
|
||||
currentPresences = currentPresences.filter((m) => presenceRefsToRemove.indexOf(m.presence_ref) < 0);
|
||||
state[key] = currentPresences;
|
||||
onLeave(key, currentPresences, leftPresences);
|
||||
if (currentPresences.length === 0)
|
||||
delete state[key];
|
||||
});
|
||||
return state;
|
||||
}
|
||||
/** @internal */
|
||||
static map(obj, func) {
|
||||
return Object.getOwnPropertyNames(obj).map((key) => func(key, obj[key]));
|
||||
}
|
||||
/**
|
||||
* Remove 'metas' key
|
||||
* Change 'phx_ref' to 'presence_ref'
|
||||
* Remove 'phx_ref' and 'phx_ref_prev'
|
||||
*
|
||||
* @example
|
||||
* // returns {
|
||||
* abc123: [
|
||||
* { presence_ref: '2', user_id: 1 },
|
||||
* { presence_ref: '3', user_id: 2 }
|
||||
* ]
|
||||
* }
|
||||
* RealtimePresence.transformState({
|
||||
* abc123: {
|
||||
* metas: [
|
||||
* { phx_ref: '2', phx_ref_prev: '1' user_id: 1 },
|
||||
* { phx_ref: '3', user_id: 2 }
|
||||
* ]
|
||||
* }
|
||||
* })
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static transformState(state) {
|
||||
state = this.cloneDeep(state);
|
||||
return Object.getOwnPropertyNames(state).reduce((newState, key) => {
|
||||
const presences = state[key];
|
||||
if ('metas' in presences) {
|
||||
newState[key] = presences.metas.map((presence) => {
|
||||
presence['presence_ref'] = presence['phx_ref'];
|
||||
delete presence['phx_ref'];
|
||||
delete presence['phx_ref_prev'];
|
||||
return presence;
|
||||
});
|
||||
}
|
||||
else {
|
||||
newState[key] = presences;
|
||||
}
|
||||
return newState;
|
||||
}, {});
|
||||
}
|
||||
/** @internal */
|
||||
static cloneDeep(obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
/** @internal */
|
||||
onJoin(callback) {
|
||||
this.caller.onJoin = callback;
|
||||
}
|
||||
/** @internal */
|
||||
onLeave(callback) {
|
||||
this.caller.onLeave = callback;
|
||||
}
|
||||
/** @internal */
|
||||
onSync(callback) {
|
||||
this.caller.onSync = callback;
|
||||
}
|
||||
/** @internal */
|
||||
inPendingSyncState() {
|
||||
return !this.joinRef || this.joinRef !== this.channel._joinRef();
|
||||
}
|
||||
}
|
||||
exports.default = RealtimePresence;
|
||||
//# sourceMappingURL=RealtimePresence.js.map
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
"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 (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__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_PRESENCE_LISTEN_EVENTS = exports.REALTIME_POSTGRES_CHANGES_LISTEN_EVENT = exports.REALTIME_LISTEN_TYPES = exports.RealtimeClient = exports.RealtimeChannel = exports.RealtimePresence = void 0;
|
||||
const RealtimeClient_1 = __importDefault(require("./RealtimeClient"));
|
||||
exports.RealtimeClient = RealtimeClient_1.default;
|
||||
const RealtimeChannel_1 = __importStar(require("./RealtimeChannel"));
|
||||
exports.RealtimeChannel = RealtimeChannel_1.default;
|
||||
Object.defineProperty(exports, "REALTIME_LISTEN_TYPES", { enumerable: true, get: function () { return RealtimeChannel_1.REALTIME_LISTEN_TYPES; } });
|
||||
Object.defineProperty(exports, "REALTIME_POSTGRES_CHANGES_LISTEN_EVENT", { enumerable: true, get: function () { return RealtimeChannel_1.REALTIME_POSTGRES_CHANGES_LISTEN_EVENT; } });
|
||||
Object.defineProperty(exports, "REALTIME_SUBSCRIBE_STATES", { enumerable: true, get: function () { return RealtimeChannel_1.REALTIME_SUBSCRIBE_STATES; } });
|
||||
Object.defineProperty(exports, "REALTIME_CHANNEL_STATES", { enumerable: true, get: function () { return RealtimeChannel_1.REALTIME_CHANNEL_STATES; } });
|
||||
const RealtimePresence_1 = __importStar(require("./RealtimePresence"));
|
||||
exports.RealtimePresence = RealtimePresence_1.default;
|
||||
Object.defineProperty(exports, "REALTIME_PRESENCE_LISTEN_EVENTS", { enumerable: true, get: function () { return RealtimePresence_1.REALTIME_PRESENCE_LISTEN_EVENTS; } });
|
||||
//# sourceMappingURL=index.js.map
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.CONNECTION_STATE = exports.TRANSPORTS = exports.CHANNEL_EVENTS = exports.CHANNEL_STATES = exports.SOCKET_STATES = exports.WS_CLOSE_NORMAL = exports.DEFAULT_TIMEOUT = exports.VSN = exports.DEFAULT_HEADERS = void 0;
|
||||
const version_1 = require("./version");
|
||||
exports.DEFAULT_HEADERS = { 'X-Client-Info': `realtime-js/${version_1.version}` };
|
||||
exports.VSN = '1.0.0';
|
||||
exports.DEFAULT_TIMEOUT = 10000;
|
||||
exports.WS_CLOSE_NORMAL = 1000;
|
||||
var SOCKET_STATES;
|
||||
(function (SOCKET_STATES) {
|
||||
SOCKET_STATES[SOCKET_STATES["connecting"] = 0] = "connecting";
|
||||
SOCKET_STATES[SOCKET_STATES["open"] = 1] = "open";
|
||||
SOCKET_STATES[SOCKET_STATES["closing"] = 2] = "closing";
|
||||
SOCKET_STATES[SOCKET_STATES["closed"] = 3] = "closed";
|
||||
})(SOCKET_STATES = exports.SOCKET_STATES || (exports.SOCKET_STATES = {}));
|
||||
var CHANNEL_STATES;
|
||||
(function (CHANNEL_STATES) {
|
||||
CHANNEL_STATES["closed"] = "closed";
|
||||
CHANNEL_STATES["errored"] = "errored";
|
||||
CHANNEL_STATES["joined"] = "joined";
|
||||
CHANNEL_STATES["joining"] = "joining";
|
||||
CHANNEL_STATES["leaving"] = "leaving";
|
||||
})(CHANNEL_STATES = exports.CHANNEL_STATES || (exports.CHANNEL_STATES = {}));
|
||||
var CHANNEL_EVENTS;
|
||||
(function (CHANNEL_EVENTS) {
|
||||
CHANNEL_EVENTS["close"] = "phx_close";
|
||||
CHANNEL_EVENTS["error"] = "phx_error";
|
||||
CHANNEL_EVENTS["join"] = "phx_join";
|
||||
CHANNEL_EVENTS["reply"] = "phx_reply";
|
||||
CHANNEL_EVENTS["leave"] = "phx_leave";
|
||||
CHANNEL_EVENTS["access_token"] = "access_token";
|
||||
})(CHANNEL_EVENTS = exports.CHANNEL_EVENTS || (exports.CHANNEL_EVENTS = {}));
|
||||
var TRANSPORTS;
|
||||
(function (TRANSPORTS) {
|
||||
TRANSPORTS["websocket"] = "websocket";
|
||||
})(TRANSPORTS = exports.TRANSPORTS || (exports.TRANSPORTS = {}));
|
||||
var CONNECTION_STATE;
|
||||
(function (CONNECTION_STATE) {
|
||||
CONNECTION_STATE["Connecting"] = "connecting";
|
||||
CONNECTION_STATE["Open"] = "open";
|
||||
CONNECTION_STATE["Closing"] = "closing";
|
||||
CONNECTION_STATE["Closed"] = "closed";
|
||||
})(CONNECTION_STATE = exports.CONNECTION_STATE || (exports.CONNECTION_STATE = {}));
|
||||
//# sourceMappingURL=constants.js.map
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const constants_1 = require("../lib/constants");
|
||||
class Push {
|
||||
/**
|
||||
* Initializes the Push
|
||||
*
|
||||
* @param channel The Channel
|
||||
* @param event The event, for example `"phx_join"`
|
||||
* @param payload The payload, for example `{user_id: 123}`
|
||||
* @param timeout The push timeout in milliseconds
|
||||
*/
|
||||
constructor(channel, event, payload = {}, timeout = constants_1.DEFAULT_TIMEOUT) {
|
||||
this.channel = channel;
|
||||
this.event = event;
|
||||
this.payload = payload;
|
||||
this.timeout = timeout;
|
||||
this.sent = false;
|
||||
this.timeoutTimer = undefined;
|
||||
this.ref = '';
|
||||
this.receivedResp = null;
|
||||
this.recHooks = [];
|
||||
this.refEvent = null;
|
||||
}
|
||||
resend(timeout) {
|
||||
this.timeout = timeout;
|
||||
this._cancelRefEvent();
|
||||
this.ref = '';
|
||||
this.refEvent = null;
|
||||
this.receivedResp = null;
|
||||
this.sent = false;
|
||||
this.send();
|
||||
}
|
||||
send() {
|
||||
if (this._hasReceived('timeout')) {
|
||||
return;
|
||||
}
|
||||
this.startTimeout();
|
||||
this.sent = true;
|
||||
this.channel.socket.push({
|
||||
topic: this.channel.topic,
|
||||
event: this.event,
|
||||
payload: this.payload,
|
||||
ref: this.ref,
|
||||
join_ref: this.channel._joinRef(),
|
||||
});
|
||||
}
|
||||
updatePayload(payload) {
|
||||
this.payload = Object.assign(Object.assign({}, this.payload), payload);
|
||||
}
|
||||
receive(status, callback) {
|
||||
var _a;
|
||||
if (this._hasReceived(status)) {
|
||||
callback((_a = this.receivedResp) === null || _a === void 0 ? void 0 : _a.response);
|
||||
}
|
||||
this.recHooks.push({ status, callback });
|
||||
return this;
|
||||
}
|
||||
startTimeout() {
|
||||
if (this.timeoutTimer) {
|
||||
return;
|
||||
}
|
||||
this.ref = this.channel.socket._makeRef();
|
||||
this.refEvent = this.channel._replyEventName(this.ref);
|
||||
const callback = (payload) => {
|
||||
this._cancelRefEvent();
|
||||
this._cancelTimeout();
|
||||
this.receivedResp = payload;
|
||||
this._matchReceive(payload);
|
||||
};
|
||||
this.channel._on(this.refEvent, {}, callback);
|
||||
this.timeoutTimer = setTimeout(() => {
|
||||
this.trigger('timeout', {});
|
||||
}, this.timeout);
|
||||
}
|
||||
trigger(status, response) {
|
||||
if (this.refEvent)
|
||||
this.channel._trigger(this.refEvent, { status, response });
|
||||
}
|
||||
destroy() {
|
||||
this._cancelRefEvent();
|
||||
this._cancelTimeout();
|
||||
}
|
||||
_cancelRefEvent() {
|
||||
if (!this.refEvent) {
|
||||
return;
|
||||
}
|
||||
this.channel._off(this.refEvent, {});
|
||||
}
|
||||
_cancelTimeout() {
|
||||
clearTimeout(this.timeoutTimer);
|
||||
this.timeoutTimer = undefined;
|
||||
}
|
||||
_matchReceive({ status, response, }) {
|
||||
this.recHooks
|
||||
.filter((h) => h.status === status)
|
||||
.forEach((h) => h.callback(response));
|
||||
}
|
||||
_hasReceived(status) {
|
||||
return this.receivedResp && this.receivedResp.status === status;
|
||||
}
|
||||
}
|
||||
exports.default = Push;
|
||||
//# sourceMappingURL=push.js.map
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
"use strict";
|
||||
// This file draws heavily from https://github.com/phoenixframework/phoenix/commit/cf098e9cf7a44ee6479d31d911a97d3c7430c6fe
|
||||
// License: https://github.com/phoenixframework/phoenix/blob/master/LICENSE.md
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
class Serializer {
|
||||
constructor() {
|
||||
this.HEADER_LENGTH = 1;
|
||||
}
|
||||
decode(rawPayload, callback) {
|
||||
if (rawPayload.constructor === ArrayBuffer) {
|
||||
return callback(this._binaryDecode(rawPayload));
|
||||
}
|
||||
if (typeof rawPayload === 'string') {
|
||||
return callback(JSON.parse(rawPayload));
|
||||
}
|
||||
return callback({});
|
||||
}
|
||||
_binaryDecode(buffer) {
|
||||
const view = new DataView(buffer);
|
||||
const decoder = new TextDecoder();
|
||||
return this._decodeBroadcast(buffer, view, decoder);
|
||||
}
|
||||
_decodeBroadcast(buffer, view, decoder) {
|
||||
const topicSize = view.getUint8(1);
|
||||
const eventSize = view.getUint8(2);
|
||||
let offset = this.HEADER_LENGTH + 2;
|
||||
const topic = decoder.decode(buffer.slice(offset, offset + topicSize));
|
||||
offset = offset + topicSize;
|
||||
const event = decoder.decode(buffer.slice(offset, offset + eventSize));
|
||||
offset = offset + eventSize;
|
||||
const data = JSON.parse(decoder.decode(buffer.slice(offset, buffer.byteLength)));
|
||||
return { ref: null, topic: topic, event: event, payload: data };
|
||||
}
|
||||
}
|
||||
exports.default = Serializer;
|
||||
//# sourceMappingURL=serializer.js.map
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
/**
|
||||
* Creates a timer that accepts a `timerCalc` function to perform calculated timeout retries, such as exponential backoff.
|
||||
*
|
||||
* @example
|
||||
* let reconnectTimer = new Timer(() => this.connect(), function(tries){
|
||||
* return [1000, 5000, 10000][tries - 1] || 10000
|
||||
* })
|
||||
* reconnectTimer.scheduleTimeout() // fires after 1000
|
||||
* reconnectTimer.scheduleTimeout() // fires after 5000
|
||||
* reconnectTimer.reset()
|
||||
* reconnectTimer.scheduleTimeout() // fires after 1000
|
||||
*/
|
||||
class Timer {
|
||||
constructor(callback, timerCalc) {
|
||||
this.callback = callback;
|
||||
this.timerCalc = timerCalc;
|
||||
this.timer = undefined;
|
||||
this.tries = 0;
|
||||
this.callback = callback;
|
||||
this.timerCalc = timerCalc;
|
||||
}
|
||||
reset() {
|
||||
this.tries = 0;
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
// Cancels any previous scheduleTimeout and schedules callback
|
||||
scheduleTimeout() {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => {
|
||||
this.tries = this.tries + 1;
|
||||
this.callback();
|
||||
}, this.timerCalc(this.tries + 1));
|
||||
}
|
||||
}
|
||||
exports.default = Timer;
|
||||
//# sourceMappingURL=timer.js.map
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Helpers to convert the change Payload into native JS types.
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.httpEndpointURL = exports.toTimestampString = exports.toArray = exports.toJson = exports.toNumber = exports.toBoolean = exports.convertCell = exports.convertColumn = exports.convertChangeData = exports.PostgresTypes = void 0;
|
||||
// Adapted from epgsql (src/epgsql_binary.erl), this module licensed under
|
||||
// 3-clause BSD found here: https://raw.githubusercontent.com/epgsql/epgsql/devel/LICENSE
|
||||
var PostgresTypes;
|
||||
(function (PostgresTypes) {
|
||||
PostgresTypes["abstime"] = "abstime";
|
||||
PostgresTypes["bool"] = "bool";
|
||||
PostgresTypes["date"] = "date";
|
||||
PostgresTypes["daterange"] = "daterange";
|
||||
PostgresTypes["float4"] = "float4";
|
||||
PostgresTypes["float8"] = "float8";
|
||||
PostgresTypes["int2"] = "int2";
|
||||
PostgresTypes["int4"] = "int4";
|
||||
PostgresTypes["int4range"] = "int4range";
|
||||
PostgresTypes["int8"] = "int8";
|
||||
PostgresTypes["int8range"] = "int8range";
|
||||
PostgresTypes["json"] = "json";
|
||||
PostgresTypes["jsonb"] = "jsonb";
|
||||
PostgresTypes["money"] = "money";
|
||||
PostgresTypes["numeric"] = "numeric";
|
||||
PostgresTypes["oid"] = "oid";
|
||||
PostgresTypes["reltime"] = "reltime";
|
||||
PostgresTypes["text"] = "text";
|
||||
PostgresTypes["time"] = "time";
|
||||
PostgresTypes["timestamp"] = "timestamp";
|
||||
PostgresTypes["timestamptz"] = "timestamptz";
|
||||
PostgresTypes["timetz"] = "timetz";
|
||||
PostgresTypes["tsrange"] = "tsrange";
|
||||
PostgresTypes["tstzrange"] = "tstzrange";
|
||||
})(PostgresTypes = exports.PostgresTypes || (exports.PostgresTypes = {}));
|
||||
/**
|
||||
* Takes an array of columns and an object of string values then converts each string value
|
||||
* to its mapped type.
|
||||
*
|
||||
* @param {{name: String, type: String}[]} columns
|
||||
* @param {Object} record
|
||||
* @param {Object} options The map of various options that can be applied to the mapper
|
||||
* @param {Array} options.skipTypes The array of types that should not be converted
|
||||
*
|
||||
* @example convertChangeData([{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age:'33'}, {})
|
||||
* //=>{ first_name: 'Paul', age: 33 }
|
||||
*/
|
||||
const convertChangeData = (columns, record, options = {}) => {
|
||||
var _a;
|
||||
const skipTypes = (_a = options.skipTypes) !== null && _a !== void 0 ? _a : [];
|
||||
return Object.keys(record).reduce((acc, rec_key) => {
|
||||
acc[rec_key] = (0, exports.convertColumn)(rec_key, columns, record, skipTypes);
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
exports.convertChangeData = convertChangeData;
|
||||
/**
|
||||
* Converts the value of an individual column.
|
||||
*
|
||||
* @param {String} columnName The column that you want to convert
|
||||
* @param {{name: String, type: String}[]} columns All of the columns
|
||||
* @param {Object} record The map of string values
|
||||
* @param {Array} skipTypes An array of types that should not be converted
|
||||
* @return {object} Useless information
|
||||
*
|
||||
* @example convertColumn('age', [{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age: '33'}, [])
|
||||
* //=> 33
|
||||
* @example convertColumn('age', [{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age: '33'}, ['int4'])
|
||||
* //=> "33"
|
||||
*/
|
||||
const convertColumn = (columnName, columns, record, skipTypes) => {
|
||||
const column = columns.find((x) => x.name === columnName);
|
||||
const colType = column === null || column === void 0 ? void 0 : column.type;
|
||||
const value = record[columnName];
|
||||
if (colType && !skipTypes.includes(colType)) {
|
||||
return (0, exports.convertCell)(colType, value);
|
||||
}
|
||||
return noop(value);
|
||||
};
|
||||
exports.convertColumn = convertColumn;
|
||||
/**
|
||||
* If the value of the cell is `null`, returns null.
|
||||
* Otherwise converts the string value to the correct type.
|
||||
* @param {String} type A postgres column type
|
||||
* @param {String} value The cell value
|
||||
*
|
||||
* @example convertCell('bool', 't')
|
||||
* //=> true
|
||||
* @example convertCell('int8', '10')
|
||||
* //=> 10
|
||||
* @example convertCell('_int4', '{1,2,3,4}')
|
||||
* //=> [1,2,3,4]
|
||||
*/
|
||||
const convertCell = (type, value) => {
|
||||
// if data type is an array
|
||||
if (type.charAt(0) === '_') {
|
||||
const dataType = type.slice(1, type.length);
|
||||
return (0, exports.toArray)(value, dataType);
|
||||
}
|
||||
// If not null, convert to correct type.
|
||||
switch (type) {
|
||||
case PostgresTypes.bool:
|
||||
return (0, exports.toBoolean)(value);
|
||||
case PostgresTypes.float4:
|
||||
case PostgresTypes.float8:
|
||||
case PostgresTypes.int2:
|
||||
case PostgresTypes.int4:
|
||||
case PostgresTypes.int8:
|
||||
case PostgresTypes.numeric:
|
||||
case PostgresTypes.oid:
|
||||
return (0, exports.toNumber)(value);
|
||||
case PostgresTypes.json:
|
||||
case PostgresTypes.jsonb:
|
||||
return (0, exports.toJson)(value);
|
||||
case PostgresTypes.timestamp:
|
||||
return (0, exports.toTimestampString)(value); // Format to be consistent with PostgREST
|
||||
case PostgresTypes.abstime: // To allow users to cast it based on Timezone
|
||||
case PostgresTypes.date: // To allow users to cast it based on Timezone
|
||||
case PostgresTypes.daterange:
|
||||
case PostgresTypes.int4range:
|
||||
case PostgresTypes.int8range:
|
||||
case PostgresTypes.money:
|
||||
case PostgresTypes.reltime: // To allow users to cast it based on Timezone
|
||||
case PostgresTypes.text:
|
||||
case PostgresTypes.time: // To allow users to cast it based on Timezone
|
||||
case PostgresTypes.timestamptz: // To allow users to cast it based on Timezone
|
||||
case PostgresTypes.timetz: // To allow users to cast it based on Timezone
|
||||
case PostgresTypes.tsrange:
|
||||
case PostgresTypes.tstzrange:
|
||||
return noop(value);
|
||||
default:
|
||||
// Return the value for remaining types
|
||||
return noop(value);
|
||||
}
|
||||
};
|
||||
exports.convertCell = convertCell;
|
||||
const noop = (value) => {
|
||||
return value;
|
||||
};
|
||||
const toBoolean = (value) => {
|
||||
switch (value) {
|
||||
case 't':
|
||||
return true;
|
||||
case 'f':
|
||||
return false;
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
};
|
||||
exports.toBoolean = toBoolean;
|
||||
const toNumber = (value) => {
|
||||
if (typeof value === 'string') {
|
||||
const parsedValue = parseFloat(value);
|
||||
if (!Number.isNaN(parsedValue)) {
|
||||
return parsedValue;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
};
|
||||
exports.toNumber = toNumber;
|
||||
const toJson = (value) => {
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
catch (error) {
|
||||
console.log(`JSON parse error: ${error}`);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
};
|
||||
exports.toJson = toJson;
|
||||
/**
|
||||
* Converts a Postgres Array into a native JS array
|
||||
*
|
||||
* @example toArray('{}', 'int4')
|
||||
* //=> []
|
||||
* @example toArray('{"[2021-01-01,2021-12-31)","(2021-01-01,2021-12-32]"}', 'daterange')
|
||||
* //=> ['[2021-01-01,2021-12-31)', '(2021-01-01,2021-12-32]']
|
||||
* @example toArray([1,2,3,4], 'int4')
|
||||
* //=> [1,2,3,4]
|
||||
*/
|
||||
const toArray = (value, type) => {
|
||||
if (typeof value !== 'string') {
|
||||
return value;
|
||||
}
|
||||
const lastIdx = value.length - 1;
|
||||
const closeBrace = value[lastIdx];
|
||||
const openBrace = value[0];
|
||||
// Confirm value is a Postgres array by checking curly brackets
|
||||
if (openBrace === '{' && closeBrace === '}') {
|
||||
let arr;
|
||||
const valTrim = value.slice(1, lastIdx);
|
||||
// TODO: find a better solution to separate Postgres array data
|
||||
try {
|
||||
arr = JSON.parse('[' + valTrim + ']');
|
||||
}
|
||||
catch (_) {
|
||||
// WARNING: splitting on comma does not cover all edge cases
|
||||
arr = valTrim ? valTrim.split(',') : [];
|
||||
}
|
||||
return arr.map((val) => (0, exports.convertCell)(type, val));
|
||||
}
|
||||
return value;
|
||||
};
|
||||
exports.toArray = toArray;
|
||||
/**
|
||||
* Fixes timestamp to be ISO-8601. Swaps the space between the date and time for a 'T'
|
||||
* See https://github.com/supabase/supabase/issues/18
|
||||
*
|
||||
* @example toTimestampString('2019-09-10 00:00:00')
|
||||
* //=> '2019-09-10T00:00:00'
|
||||
*/
|
||||
const toTimestampString = (value) => {
|
||||
if (typeof value === 'string') {
|
||||
return value.replace(' ', 'T');
|
||||
}
|
||||
return value;
|
||||
};
|
||||
exports.toTimestampString = toTimestampString;
|
||||
const httpEndpointURL = (socketUrl) => {
|
||||
let url = socketUrl;
|
||||
url = url.replace(/^ws/i, 'http');
|
||||
url = url.replace(/(\/socket\/websocket|\/socket|\/websocket)\/?$/i, '');
|
||||
return url.replace(/\/+$/, '');
|
||||
};
|
||||
exports.httpEndpointURL = httpEndpointURL;
|
||||
//# sourceMappingURL=transformers.js.map
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.version = void 0;
|
||||
exports.version = '2.11.2';
|
||||
//# sourceMappingURL=version.js.map
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "@supabase/realtime-js",
|
||||
"version": "2.11.2",
|
||||
"description": "Listen to realtime updates to your PostgreSQL database",
|
||||
"keywords": [
|
||||
"realtime",
|
||||
"phoenix",
|
||||
"elixir",
|
||||
"javascript",
|
||||
"typescript",
|
||||
"firebase",
|
||||
"supabase"
|
||||
],
|
||||
"homepage": "https://github.com/supabase/realtime-js",
|
||||
"bugs": "https://github.com/supabase/realtime-js/issues",
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"main": "dist/main/index.js",
|
||||
"module": "dist/module/index.js",
|
||||
"types": "dist/module/index.d.ts",
|
||||
"repository": "https://github.com/supabase/realtime-js",
|
||||
"author": "Supabase",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"clean": "rimraf dist docs/v2",
|
||||
"format": "prettier --write \"{src,test}/**/*.ts\"",
|
||||
"build": "run-s clean format build:*",
|
||||
"build:main": "tsc -p tsconfig.json",
|
||||
"build:module": "tsc -p tsconfig.module.json",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"coverage": "vitest run --coverage",
|
||||
"docs": "typedoc src/index.ts --out docs/v2",
|
||||
"docs:json": "typedoc --json docs/v2/spec.json --excludeExternals src/index.ts",
|
||||
"check-exports": "attw --pack .",
|
||||
"ci": "run-s test coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@supabase/node-fetch": "^2.6.14",
|
||||
"@types/phoenix": "^1.5.4",
|
||||
"@types/ws": "^8.5.10",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@arethetypeswrong/cli": "^0.16.2",
|
||||
"@types/sinon": "^17.0.3",
|
||||
"@vitest/coverage-v8": "^2.0.5",
|
||||
"eslint": "^7.0.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.1.2",
|
||||
"semantic-release-plugin-update-version-in-files": "^1.1.0",
|
||||
"sinon": "^18.0.0",
|
||||
"typedoc": "^0.22.16",
|
||||
"typescript": "^4.0.3",
|
||||
"vitest": "^2.0.5",
|
||||
"web-worker": "1.2.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user