added sale-price
This commit is contained in:
+7
@@ -0,0 +1,7 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const tslib_1 = require("tslib");
|
||||
const GoTrueAdminApi_1 = tslib_1.__importDefault(require("./GoTrueAdminApi"));
|
||||
const AuthAdminApi = GoTrueAdminApi_1.default;
|
||||
exports.default = AuthAdminApi;
|
||||
//# sourceMappingURL=AuthAdminApi.js.map
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const tslib_1 = require("tslib");
|
||||
const GoTrueClient_1 = tslib_1.__importDefault(require("./GoTrueClient"));
|
||||
const AuthClient = GoTrueClient_1.default;
|
||||
exports.default = AuthClient;
|
||||
//# sourceMappingURL=AuthClient.js.map
|
||||
+441
@@ -0,0 +1,441 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const tslib_1 = require("tslib");
|
||||
const fetch_1 = require("./lib/fetch");
|
||||
const helpers_1 = require("./lib/helpers");
|
||||
const types_1 = require("./lib/types");
|
||||
const errors_1 = require("./lib/errors");
|
||||
class GoTrueAdminApi {
|
||||
/**
|
||||
* Creates an admin API client that can be used to manage users and OAuth clients.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { GoTrueAdminApi } from '@supabase/auth-js'
|
||||
*
|
||||
* const admin = new GoTrueAdminApi({
|
||||
* url: 'https://xyzcompany.supabase.co/auth/v1',
|
||||
* headers: { Authorization: `Bearer ${process.env.SUPABASE_SERVICE_ROLE_KEY}` },
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
constructor({ url = '', headers = {}, fetch, }) {
|
||||
this.url = url;
|
||||
this.headers = headers;
|
||||
this.fetch = (0, helpers_1.resolveFetch)(fetch);
|
||||
this.mfa = {
|
||||
listFactors: this._listFactors.bind(this),
|
||||
deleteFactor: this._deleteFactor.bind(this),
|
||||
};
|
||||
this.oauth = {
|
||||
listClients: this._listOAuthClients.bind(this),
|
||||
createClient: this._createOAuthClient.bind(this),
|
||||
getClient: this._getOAuthClient.bind(this),
|
||||
updateClient: this._updateOAuthClient.bind(this),
|
||||
deleteClient: this._deleteOAuthClient.bind(this),
|
||||
regenerateClientSecret: this._regenerateOAuthClientSecret.bind(this),
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Removes a logged-in session.
|
||||
* @param jwt A valid, logged-in JWT.
|
||||
* @param scope The logout sope.
|
||||
*/
|
||||
async signOut(jwt, scope = types_1.SIGN_OUT_SCOPES[0]) {
|
||||
if (types_1.SIGN_OUT_SCOPES.indexOf(scope) < 0) {
|
||||
throw new Error(`@supabase/auth-js: Parameter scope must be one of ${types_1.SIGN_OUT_SCOPES.join(', ')}`);
|
||||
}
|
||||
try {
|
||||
await (0, fetch_1._request)(this.fetch, 'POST', `${this.url}/logout?scope=${scope}`, {
|
||||
headers: this.headers,
|
||||
jwt,
|
||||
noResolveJson: true,
|
||||
});
|
||||
return { data: null, error: null };
|
||||
}
|
||||
catch (error) {
|
||||
if ((0, errors_1.isAuthError)(error)) {
|
||||
return { data: null, error };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Sends an invite link to an email address.
|
||||
* @param email The email address of the user.
|
||||
* @param options Additional options to be included when inviting.
|
||||
*/
|
||||
async inviteUserByEmail(email, options = {}) {
|
||||
try {
|
||||
return await (0, fetch_1._request)(this.fetch, 'POST', `${this.url}/invite`, {
|
||||
body: { email, data: options.data },
|
||||
headers: this.headers,
|
||||
redirectTo: options.redirectTo,
|
||||
xform: fetch_1._userResponse,
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
if ((0, errors_1.isAuthError)(error)) {
|
||||
return { data: { user: null }, error };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Generates email links and OTPs to be sent via a custom email provider.
|
||||
* @param email The user's email.
|
||||
* @param options.password User password. For signup only.
|
||||
* @param options.data Optional user metadata. For signup only.
|
||||
* @param options.redirectTo The redirect url which should be appended to the generated link
|
||||
*/
|
||||
async generateLink(params) {
|
||||
try {
|
||||
const { options } = params, rest = tslib_1.__rest(params, ["options"]);
|
||||
const body = Object.assign(Object.assign({}, rest), options);
|
||||
if ('newEmail' in rest) {
|
||||
// replace newEmail with new_email in request body
|
||||
body.new_email = rest === null || rest === void 0 ? void 0 : rest.newEmail;
|
||||
delete body['newEmail'];
|
||||
}
|
||||
return await (0, fetch_1._request)(this.fetch, 'POST', `${this.url}/admin/generate_link`, {
|
||||
body: body,
|
||||
headers: this.headers,
|
||||
xform: fetch_1._generateLinkResponse,
|
||||
redirectTo: options === null || options === void 0 ? void 0 : options.redirectTo,
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
if ((0, errors_1.isAuthError)(error)) {
|
||||
return {
|
||||
data: {
|
||||
properties: null,
|
||||
user: null,
|
||||
},
|
||||
error,
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
// User Admin API
|
||||
/**
|
||||
* Creates a new user.
|
||||
* This function should only be called on a server. Never expose your `service_role` key in the browser.
|
||||
*/
|
||||
async createUser(attributes) {
|
||||
try {
|
||||
return await (0, fetch_1._request)(this.fetch, 'POST', `${this.url}/admin/users`, {
|
||||
body: attributes,
|
||||
headers: this.headers,
|
||||
xform: fetch_1._userResponse,
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
if ((0, errors_1.isAuthError)(error)) {
|
||||
return { data: { user: null }, error };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get a list of users.
|
||||
*
|
||||
* This function should only be called on a server. Never expose your `service_role` key in the browser.
|
||||
* @param params An object which supports `page` and `perPage` as numbers, to alter the paginated results.
|
||||
*/
|
||||
async listUsers(params) {
|
||||
var _a, _b, _c, _d, _e, _f, _g;
|
||||
try {
|
||||
const pagination = { nextPage: null, lastPage: 0, total: 0 };
|
||||
const response = await (0, fetch_1._request)(this.fetch, 'GET', `${this.url}/admin/users`, {
|
||||
headers: this.headers,
|
||||
noResolveJson: true,
|
||||
query: {
|
||||
page: (_b = (_a = params === null || params === void 0 ? void 0 : params.page) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : '',
|
||||
per_page: (_d = (_c = params === null || params === void 0 ? void 0 : params.perPage) === null || _c === void 0 ? void 0 : _c.toString()) !== null && _d !== void 0 ? _d : '',
|
||||
},
|
||||
xform: fetch_1._noResolveJsonResponse,
|
||||
});
|
||||
if (response.error)
|
||||
throw response.error;
|
||||
const users = await response.json();
|
||||
const total = (_e = response.headers.get('x-total-count')) !== null && _e !== void 0 ? _e : 0;
|
||||
const links = (_g = (_f = response.headers.get('link')) === null || _f === void 0 ? void 0 : _f.split(',')) !== null && _g !== void 0 ? _g : [];
|
||||
if (links.length > 0) {
|
||||
links.forEach((link) => {
|
||||
const page = parseInt(link.split(';')[0].split('=')[1].substring(0, 1));
|
||||
const rel = JSON.parse(link.split(';')[1].split('=')[1]);
|
||||
pagination[`${rel}Page`] = page;
|
||||
});
|
||||
pagination.total = parseInt(total);
|
||||
}
|
||||
return { data: Object.assign(Object.assign({}, users), pagination), error: null };
|
||||
}
|
||||
catch (error) {
|
||||
if ((0, errors_1.isAuthError)(error)) {
|
||||
return { data: { users: [] }, error };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get user by id.
|
||||
*
|
||||
* @param uid The user's unique identifier
|
||||
*
|
||||
* This function should only be called on a server. Never expose your `service_role` key in the browser.
|
||||
*/
|
||||
async getUserById(uid) {
|
||||
(0, helpers_1.validateUUID)(uid);
|
||||
try {
|
||||
return await (0, fetch_1._request)(this.fetch, 'GET', `${this.url}/admin/users/${uid}`, {
|
||||
headers: this.headers,
|
||||
xform: fetch_1._userResponse,
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
if ((0, errors_1.isAuthError)(error)) {
|
||||
return { data: { user: null }, error };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Updates the user data. Changes are applied directly without confirmation flows.
|
||||
*
|
||||
* @param attributes The data you want to update.
|
||||
*
|
||||
* This function should only be called on a server. Never expose your `service_role` key in the browser.
|
||||
*/
|
||||
async updateUserById(uid, attributes) {
|
||||
(0, helpers_1.validateUUID)(uid);
|
||||
try {
|
||||
return await (0, fetch_1._request)(this.fetch, 'PUT', `${this.url}/admin/users/${uid}`, {
|
||||
body: attributes,
|
||||
headers: this.headers,
|
||||
xform: fetch_1._userResponse,
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
if ((0, errors_1.isAuthError)(error)) {
|
||||
return { data: { user: null }, error };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Delete a user. Requires a `service_role` key.
|
||||
*
|
||||
* @param id The user id you want to remove.
|
||||
* @param shouldSoftDelete If true, then the user will be soft-deleted from the auth schema. Soft deletion allows user identification from the hashed user ID but is not reversible.
|
||||
* Defaults to false for backward compatibility.
|
||||
*
|
||||
* This function should only be called on a server. Never expose your `service_role` key in the browser.
|
||||
*/
|
||||
async deleteUser(id, shouldSoftDelete = false) {
|
||||
(0, helpers_1.validateUUID)(id);
|
||||
try {
|
||||
return await (0, fetch_1._request)(this.fetch, 'DELETE', `${this.url}/admin/users/${id}`, {
|
||||
headers: this.headers,
|
||||
body: {
|
||||
should_soft_delete: shouldSoftDelete,
|
||||
},
|
||||
xform: fetch_1._userResponse,
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
if ((0, errors_1.isAuthError)(error)) {
|
||||
return { data: { user: null }, error };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async _listFactors(params) {
|
||||
(0, helpers_1.validateUUID)(params.userId);
|
||||
try {
|
||||
const { data, error } = await (0, fetch_1._request)(this.fetch, 'GET', `${this.url}/admin/users/${params.userId}/factors`, {
|
||||
headers: this.headers,
|
||||
xform: (factors) => {
|
||||
return { data: { factors }, error: null };
|
||||
},
|
||||
});
|
||||
return { data, error };
|
||||
}
|
||||
catch (error) {
|
||||
if ((0, errors_1.isAuthError)(error)) {
|
||||
return { data: null, error };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async _deleteFactor(params) {
|
||||
(0, helpers_1.validateUUID)(params.userId);
|
||||
(0, helpers_1.validateUUID)(params.id);
|
||||
try {
|
||||
const data = await (0, fetch_1._request)(this.fetch, 'DELETE', `${this.url}/admin/users/${params.userId}/factors/${params.id}`, {
|
||||
headers: this.headers,
|
||||
});
|
||||
return { data, error: null };
|
||||
}
|
||||
catch (error) {
|
||||
if ((0, errors_1.isAuthError)(error)) {
|
||||
return { data: null, error };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Lists all OAuth clients with optional pagination.
|
||||
* Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
|
||||
*
|
||||
* This function should only be called on a server. Never expose your `service_role` key in the browser.
|
||||
*/
|
||||
async _listOAuthClients(params) {
|
||||
var _a, _b, _c, _d, _e, _f, _g;
|
||||
try {
|
||||
const pagination = { nextPage: null, lastPage: 0, total: 0 };
|
||||
const response = await (0, fetch_1._request)(this.fetch, 'GET', `${this.url}/admin/oauth/clients`, {
|
||||
headers: this.headers,
|
||||
noResolveJson: true,
|
||||
query: {
|
||||
page: (_b = (_a = params === null || params === void 0 ? void 0 : params.page) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : '',
|
||||
per_page: (_d = (_c = params === null || params === void 0 ? void 0 : params.perPage) === null || _c === void 0 ? void 0 : _c.toString()) !== null && _d !== void 0 ? _d : '',
|
||||
},
|
||||
xform: fetch_1._noResolveJsonResponse,
|
||||
});
|
||||
if (response.error)
|
||||
throw response.error;
|
||||
const clients = await response.json();
|
||||
const total = (_e = response.headers.get('x-total-count')) !== null && _e !== void 0 ? _e : 0;
|
||||
const links = (_g = (_f = response.headers.get('link')) === null || _f === void 0 ? void 0 : _f.split(',')) !== null && _g !== void 0 ? _g : [];
|
||||
if (links.length > 0) {
|
||||
links.forEach((link) => {
|
||||
const page = parseInt(link.split(';')[0].split('=')[1].substring(0, 1));
|
||||
const rel = JSON.parse(link.split(';')[1].split('=')[1]);
|
||||
pagination[`${rel}Page`] = page;
|
||||
});
|
||||
pagination.total = parseInt(total);
|
||||
}
|
||||
return { data: Object.assign(Object.assign({}, clients), pagination), error: null };
|
||||
}
|
||||
catch (error) {
|
||||
if ((0, errors_1.isAuthError)(error)) {
|
||||
return { data: { clients: [] }, error };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Creates a new OAuth client.
|
||||
* Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
|
||||
*
|
||||
* This function should only be called on a server. Never expose your `service_role` key in the browser.
|
||||
*/
|
||||
async _createOAuthClient(params) {
|
||||
try {
|
||||
return await (0, fetch_1._request)(this.fetch, 'POST', `${this.url}/admin/oauth/clients`, {
|
||||
body: params,
|
||||
headers: this.headers,
|
||||
xform: (client) => {
|
||||
return { data: client, error: null };
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
if ((0, errors_1.isAuthError)(error)) {
|
||||
return { data: null, error };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Gets details of a specific OAuth client.
|
||||
* Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
|
||||
*
|
||||
* This function should only be called on a server. Never expose your `service_role` key in the browser.
|
||||
*/
|
||||
async _getOAuthClient(clientId) {
|
||||
try {
|
||||
return await (0, fetch_1._request)(this.fetch, 'GET', `${this.url}/admin/oauth/clients/${clientId}`, {
|
||||
headers: this.headers,
|
||||
xform: (client) => {
|
||||
return { data: client, error: null };
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
if ((0, errors_1.isAuthError)(error)) {
|
||||
return { data: null, error };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Updates an existing OAuth client.
|
||||
* Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
|
||||
*
|
||||
* This function should only be called on a server. Never expose your `service_role` key in the browser.
|
||||
*/
|
||||
async _updateOAuthClient(clientId, params) {
|
||||
try {
|
||||
return await (0, fetch_1._request)(this.fetch, 'PUT', `${this.url}/admin/oauth/clients/${clientId}`, {
|
||||
body: params,
|
||||
headers: this.headers,
|
||||
xform: (client) => {
|
||||
return { data: client, error: null };
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
if ((0, errors_1.isAuthError)(error)) {
|
||||
return { data: null, error };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Deletes an OAuth client.
|
||||
* Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
|
||||
*
|
||||
* This function should only be called on a server. Never expose your `service_role` key in the browser.
|
||||
*/
|
||||
async _deleteOAuthClient(clientId) {
|
||||
try {
|
||||
await (0, fetch_1._request)(this.fetch, 'DELETE', `${this.url}/admin/oauth/clients/${clientId}`, {
|
||||
headers: this.headers,
|
||||
noResolveJson: true,
|
||||
});
|
||||
return { data: null, error: null };
|
||||
}
|
||||
catch (error) {
|
||||
if ((0, errors_1.isAuthError)(error)) {
|
||||
return { data: null, error };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Regenerates the secret for an OAuth client.
|
||||
* Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
|
||||
*
|
||||
* This function should only be called on a server. Never expose your `service_role` key in the browser.
|
||||
*/
|
||||
async _regenerateOAuthClientSecret(clientId) {
|
||||
try {
|
||||
return await (0, fetch_1._request)(this.fetch, 'POST', `${this.url}/admin/oauth/clients/${clientId}/regenerate_secret`, {
|
||||
headers: this.headers,
|
||||
xform: (client) => {
|
||||
return { data: client, error: null };
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
if ((0, errors_1.isAuthError)(error)) {
|
||||
return { data: null, error };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.default = GoTrueAdminApi;
|
||||
//# sourceMappingURL=GoTrueAdminApi.js.map
|
||||
+2856
File diff suppressed because it is too large
Load Diff
+20
@@ -0,0 +1,20 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.processLock = exports.lockInternals = exports.NavigatorLockAcquireTimeoutError = exports.navigatorLock = exports.AuthClient = exports.AuthAdminApi = exports.GoTrueClient = exports.GoTrueAdminApi = void 0;
|
||||
const tslib_1 = require("tslib");
|
||||
const GoTrueAdminApi_1 = tslib_1.__importDefault(require("./GoTrueAdminApi"));
|
||||
exports.GoTrueAdminApi = GoTrueAdminApi_1.default;
|
||||
const GoTrueClient_1 = tslib_1.__importDefault(require("./GoTrueClient"));
|
||||
exports.GoTrueClient = GoTrueClient_1.default;
|
||||
const AuthAdminApi_1 = tslib_1.__importDefault(require("./AuthAdminApi"));
|
||||
exports.AuthAdminApi = AuthAdminApi_1.default;
|
||||
const AuthClient_1 = tslib_1.__importDefault(require("./AuthClient"));
|
||||
exports.AuthClient = AuthClient_1.default;
|
||||
tslib_1.__exportStar(require("./lib/types"), exports);
|
||||
tslib_1.__exportStar(require("./lib/errors"), exports);
|
||||
var locks_1 = require("./lib/locks");
|
||||
Object.defineProperty(exports, "navigatorLock", { enumerable: true, get: function () { return locks_1.navigatorLock; } });
|
||||
Object.defineProperty(exports, "NavigatorLockAcquireTimeoutError", { enumerable: true, get: function () { return locks_1.NavigatorLockAcquireTimeoutError; } });
|
||||
Object.defineProperty(exports, "lockInternals", { enumerable: true, get: function () { return locks_1.internals; } });
|
||||
Object.defineProperty(exports, "processLock", { enumerable: true, get: function () { return locks_1.processLock; } });
|
||||
//# sourceMappingURL=index.js.map
|
||||
+269
@@ -0,0 +1,269 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Avoid modifying this file. It's part of
|
||||
* https://github.com/supabase-community/base64url-js. Submit all fixes on
|
||||
* that repo!
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.byteToBase64URL = byteToBase64URL;
|
||||
exports.byteFromBase64URL = byteFromBase64URL;
|
||||
exports.stringToBase64URL = stringToBase64URL;
|
||||
exports.stringFromBase64URL = stringFromBase64URL;
|
||||
exports.codepointToUTF8 = codepointToUTF8;
|
||||
exports.stringToUTF8 = stringToUTF8;
|
||||
exports.stringFromUTF8 = stringFromUTF8;
|
||||
exports.base64UrlToUint8Array = base64UrlToUint8Array;
|
||||
exports.stringToUint8Array = stringToUint8Array;
|
||||
exports.bytesToBase64URL = bytesToBase64URL;
|
||||
/**
|
||||
* An array of characters that encode 6 bits into a Base64-URL alphabet
|
||||
* character.
|
||||
*/
|
||||
const TO_BASE64URL = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'.split('');
|
||||
/**
|
||||
* An array of characters that can appear in a Base64-URL encoded string but
|
||||
* should be ignored.
|
||||
*/
|
||||
const IGNORE_BASE64URL = ' \t\n\r='.split('');
|
||||
/**
|
||||
* An array of 128 numbers that map a Base64-URL character to 6 bits, or if -2
|
||||
* used to skip the character, or if -1 used to error out.
|
||||
*/
|
||||
const FROM_BASE64URL = (() => {
|
||||
const charMap = new Array(128);
|
||||
for (let i = 0; i < charMap.length; i += 1) {
|
||||
charMap[i] = -1;
|
||||
}
|
||||
for (let i = 0; i < IGNORE_BASE64URL.length; i += 1) {
|
||||
charMap[IGNORE_BASE64URL[i].charCodeAt(0)] = -2;
|
||||
}
|
||||
for (let i = 0; i < TO_BASE64URL.length; i += 1) {
|
||||
charMap[TO_BASE64URL[i].charCodeAt(0)] = i;
|
||||
}
|
||||
return charMap;
|
||||
})();
|
||||
/**
|
||||
* Converts a byte to a Base64-URL string.
|
||||
*
|
||||
* @param byte The byte to convert, or null to flush at the end of the byte sequence.
|
||||
* @param state The Base64 conversion state. Pass an initial value of `{ queue: 0, queuedBits: 0 }`.
|
||||
* @param emit A function called with the next Base64 character when ready.
|
||||
*/
|
||||
function byteToBase64URL(byte, state, emit) {
|
||||
if (byte !== null) {
|
||||
state.queue = (state.queue << 8) | byte;
|
||||
state.queuedBits += 8;
|
||||
while (state.queuedBits >= 6) {
|
||||
const pos = (state.queue >> (state.queuedBits - 6)) & 63;
|
||||
emit(TO_BASE64URL[pos]);
|
||||
state.queuedBits -= 6;
|
||||
}
|
||||
}
|
||||
else if (state.queuedBits > 0) {
|
||||
state.queue = state.queue << (6 - state.queuedBits);
|
||||
state.queuedBits = 6;
|
||||
while (state.queuedBits >= 6) {
|
||||
const pos = (state.queue >> (state.queuedBits - 6)) & 63;
|
||||
emit(TO_BASE64URL[pos]);
|
||||
state.queuedBits -= 6;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Converts a String char code (extracted using `string.charCodeAt(position)`) to a sequence of Base64-URL characters.
|
||||
*
|
||||
* @param charCode The char code of the JavaScript string.
|
||||
* @param state The Base64 state. Pass an initial value of `{ queue: 0, queuedBits: 0 }`.
|
||||
* @param emit A function called with the next byte.
|
||||
*/
|
||||
function byteFromBase64URL(charCode, state, emit) {
|
||||
const bits = FROM_BASE64URL[charCode];
|
||||
if (bits > -1) {
|
||||
// valid Base64-URL character
|
||||
state.queue = (state.queue << 6) | bits;
|
||||
state.queuedBits += 6;
|
||||
while (state.queuedBits >= 8) {
|
||||
emit((state.queue >> (state.queuedBits - 8)) & 0xff);
|
||||
state.queuedBits -= 8;
|
||||
}
|
||||
}
|
||||
else if (bits === -2) {
|
||||
// ignore spaces, tabs, newlines, =
|
||||
return;
|
||||
}
|
||||
else {
|
||||
throw new Error(`Invalid Base64-URL character "${String.fromCharCode(charCode)}"`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Converts a JavaScript string (which may include any valid character) into a
|
||||
* Base64-URL encoded string. The string is first encoded in UTF-8 which is
|
||||
* then encoded as Base64-URL.
|
||||
*
|
||||
* @param str The string to convert.
|
||||
*/
|
||||
function stringToBase64URL(str) {
|
||||
const base64 = [];
|
||||
const emitter = (char) => {
|
||||
base64.push(char);
|
||||
};
|
||||
const state = { queue: 0, queuedBits: 0 };
|
||||
stringToUTF8(str, (byte) => {
|
||||
byteToBase64URL(byte, state, emitter);
|
||||
});
|
||||
byteToBase64URL(null, state, emitter);
|
||||
return base64.join('');
|
||||
}
|
||||
/**
|
||||
* Converts a Base64-URL encoded string into a JavaScript string. It is assumed
|
||||
* that the underlying string has been encoded as UTF-8.
|
||||
*
|
||||
* @param str The Base64-URL encoded string.
|
||||
*/
|
||||
function stringFromBase64URL(str) {
|
||||
const conv = [];
|
||||
const utf8Emit = (codepoint) => {
|
||||
conv.push(String.fromCodePoint(codepoint));
|
||||
};
|
||||
const utf8State = {
|
||||
utf8seq: 0,
|
||||
codepoint: 0,
|
||||
};
|
||||
const b64State = { queue: 0, queuedBits: 0 };
|
||||
const byteEmit = (byte) => {
|
||||
stringFromUTF8(byte, utf8State, utf8Emit);
|
||||
};
|
||||
for (let i = 0; i < str.length; i += 1) {
|
||||
byteFromBase64URL(str.charCodeAt(i), b64State, byteEmit);
|
||||
}
|
||||
return conv.join('');
|
||||
}
|
||||
/**
|
||||
* Converts a Unicode codepoint to a multi-byte UTF-8 sequence.
|
||||
*
|
||||
* @param codepoint The Unicode codepoint.
|
||||
* @param emit Function which will be called for each UTF-8 byte that represents the codepoint.
|
||||
*/
|
||||
function codepointToUTF8(codepoint, emit) {
|
||||
if (codepoint <= 0x7f) {
|
||||
emit(codepoint);
|
||||
return;
|
||||
}
|
||||
else if (codepoint <= 0x7ff) {
|
||||
emit(0xc0 | (codepoint >> 6));
|
||||
emit(0x80 | (codepoint & 0x3f));
|
||||
return;
|
||||
}
|
||||
else if (codepoint <= 0xffff) {
|
||||
emit(0xe0 | (codepoint >> 12));
|
||||
emit(0x80 | ((codepoint >> 6) & 0x3f));
|
||||
emit(0x80 | (codepoint & 0x3f));
|
||||
return;
|
||||
}
|
||||
else if (codepoint <= 0x10ffff) {
|
||||
emit(0xf0 | (codepoint >> 18));
|
||||
emit(0x80 | ((codepoint >> 12) & 0x3f));
|
||||
emit(0x80 | ((codepoint >> 6) & 0x3f));
|
||||
emit(0x80 | (codepoint & 0x3f));
|
||||
return;
|
||||
}
|
||||
throw new Error(`Unrecognized Unicode codepoint: ${codepoint.toString(16)}`);
|
||||
}
|
||||
/**
|
||||
* Converts a JavaScript string to a sequence of UTF-8 bytes.
|
||||
*
|
||||
* @param str The string to convert to UTF-8.
|
||||
* @param emit Function which will be called for each UTF-8 byte of the string.
|
||||
*/
|
||||
function stringToUTF8(str, emit) {
|
||||
for (let i = 0; i < str.length; i += 1) {
|
||||
let codepoint = str.charCodeAt(i);
|
||||
if (codepoint > 0xd7ff && codepoint <= 0xdbff) {
|
||||
// most UTF-16 codepoints are Unicode codepoints, except values in this
|
||||
// range where the next UTF-16 codepoint needs to be combined with the
|
||||
// current one to get the Unicode codepoint
|
||||
const highSurrogate = ((codepoint - 0xd800) * 0x400) & 0xffff;
|
||||
const lowSurrogate = (str.charCodeAt(i + 1) - 0xdc00) & 0xffff;
|
||||
codepoint = (lowSurrogate | highSurrogate) + 0x10000;
|
||||
i += 1;
|
||||
}
|
||||
codepointToUTF8(codepoint, emit);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Converts a UTF-8 byte to a Unicode codepoint.
|
||||
*
|
||||
* @param byte The UTF-8 byte next in the sequence.
|
||||
* @param state The shared state between consecutive UTF-8 bytes in the
|
||||
* sequence, an object with the shape `{ utf8seq: 0, codepoint: 0 }`.
|
||||
* @param emit Function which will be called for each codepoint.
|
||||
*/
|
||||
function stringFromUTF8(byte, state, emit) {
|
||||
if (state.utf8seq === 0) {
|
||||
if (byte <= 0x7f) {
|
||||
emit(byte);
|
||||
return;
|
||||
}
|
||||
// count the number of 1 leading bits until you reach 0
|
||||
for (let leadingBit = 1; leadingBit < 6; leadingBit += 1) {
|
||||
if (((byte >> (7 - leadingBit)) & 1) === 0) {
|
||||
state.utf8seq = leadingBit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (state.utf8seq === 2) {
|
||||
state.codepoint = byte & 31;
|
||||
}
|
||||
else if (state.utf8seq === 3) {
|
||||
state.codepoint = byte & 15;
|
||||
}
|
||||
else if (state.utf8seq === 4) {
|
||||
state.codepoint = byte & 7;
|
||||
}
|
||||
else {
|
||||
throw new Error('Invalid UTF-8 sequence');
|
||||
}
|
||||
state.utf8seq -= 1;
|
||||
}
|
||||
else if (state.utf8seq > 0) {
|
||||
if (byte <= 0x7f) {
|
||||
throw new Error('Invalid UTF-8 sequence');
|
||||
}
|
||||
state.codepoint = (state.codepoint << 6) | (byte & 63);
|
||||
state.utf8seq -= 1;
|
||||
if (state.utf8seq === 0) {
|
||||
emit(state.codepoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Helper functions to convert different types of strings to Uint8Array
|
||||
*/
|
||||
function base64UrlToUint8Array(str) {
|
||||
const result = [];
|
||||
const state = { queue: 0, queuedBits: 0 };
|
||||
const onByte = (byte) => {
|
||||
result.push(byte);
|
||||
};
|
||||
for (let i = 0; i < str.length; i += 1) {
|
||||
byteFromBase64URL(str.charCodeAt(i), state, onByte);
|
||||
}
|
||||
return new Uint8Array(result);
|
||||
}
|
||||
function stringToUint8Array(str) {
|
||||
const result = [];
|
||||
stringToUTF8(str, (byte) => result.push(byte));
|
||||
return new Uint8Array(result);
|
||||
}
|
||||
function bytesToBase64URL(bytes) {
|
||||
const result = [];
|
||||
const state = { queue: 0, queuedBits: 0 };
|
||||
const onChar = (char) => {
|
||||
result.push(char);
|
||||
};
|
||||
bytes.forEach((byte) => byteToBase64URL(byte, state, onChar));
|
||||
// always call with `null` after processing all bytes
|
||||
byteToBase64URL(null, state, onChar);
|
||||
return result.join('');
|
||||
}
|
||||
//# sourceMappingURL=base64url.js.map
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.JWKS_TTL = exports.BASE64URL_REGEX = exports.API_VERSIONS = exports.API_VERSION_HEADER_NAME = exports.NETWORK_FAILURE = exports.DEFAULT_HEADERS = exports.AUDIENCE = exports.STORAGE_KEY = exports.GOTRUE_URL = exports.EXPIRY_MARGIN_MS = exports.AUTO_REFRESH_TICK_THRESHOLD = exports.AUTO_REFRESH_TICK_DURATION_MS = void 0;
|
||||
const version_1 = require("./version");
|
||||
/** Current session will be checked for refresh at this interval. */
|
||||
exports.AUTO_REFRESH_TICK_DURATION_MS = 30 * 1000;
|
||||
/**
|
||||
* A token refresh will be attempted this many ticks before the current session expires. */
|
||||
exports.AUTO_REFRESH_TICK_THRESHOLD = 3;
|
||||
/*
|
||||
* Earliest time before an access token expires that the session should be refreshed.
|
||||
*/
|
||||
exports.EXPIRY_MARGIN_MS = exports.AUTO_REFRESH_TICK_THRESHOLD * exports.AUTO_REFRESH_TICK_DURATION_MS;
|
||||
exports.GOTRUE_URL = 'http://localhost:9999';
|
||||
exports.STORAGE_KEY = 'supabase.auth.token';
|
||||
exports.AUDIENCE = '';
|
||||
exports.DEFAULT_HEADERS = { 'X-Client-Info': `gotrue-js/${version_1.version}` };
|
||||
exports.NETWORK_FAILURE = {
|
||||
MAX_RETRIES: 10,
|
||||
RETRY_INTERVAL: 2, // in deciseconds
|
||||
};
|
||||
exports.API_VERSION_HEADER_NAME = 'X-Supabase-Api-Version';
|
||||
exports.API_VERSIONS = {
|
||||
'2024-01-01': {
|
||||
timestamp: Date.parse('2024-01-01T00:00:00.0Z'),
|
||||
name: '2024-01-01',
|
||||
},
|
||||
};
|
||||
exports.BASE64URL_REGEX = /^([a-z0-9_-]{4})*($|[a-z0-9_-]{3}$|[a-z0-9_-]{2}$)$/i;
|
||||
exports.JWKS_TTL = 10 * 60 * 1000; // 10 minutes
|
||||
//# sourceMappingURL=constants.js.map
|
||||
+289
@@ -0,0 +1,289 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AuthInvalidJwtError = exports.AuthWeakPasswordError = exports.AuthRetryableFetchError = exports.AuthPKCECodeVerifierMissingError = exports.AuthPKCEGrantCodeExchangeError = exports.AuthImplicitGrantRedirectError = exports.AuthInvalidCredentialsError = exports.AuthInvalidTokenResponseError = exports.AuthSessionMissingError = exports.CustomAuthError = exports.AuthUnknownError = exports.AuthApiError = exports.AuthError = void 0;
|
||||
exports.isAuthError = isAuthError;
|
||||
exports.isAuthApiError = isAuthApiError;
|
||||
exports.isAuthSessionMissingError = isAuthSessionMissingError;
|
||||
exports.isAuthImplicitGrantRedirectError = isAuthImplicitGrantRedirectError;
|
||||
exports.isAuthPKCECodeVerifierMissingError = isAuthPKCECodeVerifierMissingError;
|
||||
exports.isAuthRetryableFetchError = isAuthRetryableFetchError;
|
||||
exports.isAuthWeakPasswordError = isAuthWeakPasswordError;
|
||||
/**
|
||||
* Base error thrown by Supabase Auth helpers.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { AuthError } from '@supabase/auth-js'
|
||||
*
|
||||
* throw new AuthError('Unexpected auth error', 500, 'unexpected')
|
||||
* ```
|
||||
*/
|
||||
class AuthError extends Error {
|
||||
constructor(message, status, code) {
|
||||
super(message);
|
||||
this.__isAuthError = true;
|
||||
this.name = 'AuthError';
|
||||
this.status = status;
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
exports.AuthError = AuthError;
|
||||
function isAuthError(error) {
|
||||
return typeof error === 'object' && error !== null && '__isAuthError' in error;
|
||||
}
|
||||
/**
|
||||
* Error returned directly from the GoTrue REST API.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { AuthApiError } from '@supabase/auth-js'
|
||||
*
|
||||
* throw new AuthApiError('Invalid credentials', 400, 'invalid_credentials')
|
||||
* ```
|
||||
*/
|
||||
class AuthApiError extends AuthError {
|
||||
constructor(message, status, code) {
|
||||
super(message, status, code);
|
||||
this.name = 'AuthApiError';
|
||||
this.status = status;
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
exports.AuthApiError = AuthApiError;
|
||||
function isAuthApiError(error) {
|
||||
return isAuthError(error) && error.name === 'AuthApiError';
|
||||
}
|
||||
/**
|
||||
* Wraps non-standard errors so callers can inspect the root cause.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { AuthUnknownError } from '@supabase/auth-js'
|
||||
*
|
||||
* try {
|
||||
* await someAuthCall()
|
||||
* } catch (err) {
|
||||
* throw new AuthUnknownError('Auth failed', err)
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class AuthUnknownError extends AuthError {
|
||||
constructor(message, originalError) {
|
||||
super(message);
|
||||
this.name = 'AuthUnknownError';
|
||||
this.originalError = originalError;
|
||||
}
|
||||
}
|
||||
exports.AuthUnknownError = AuthUnknownError;
|
||||
/**
|
||||
* Flexible error class used to create named auth errors at runtime.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { CustomAuthError } from '@supabase/auth-js'
|
||||
*
|
||||
* throw new CustomAuthError('My custom auth error', 'MyAuthError', 400, 'custom_code')
|
||||
* ```
|
||||
*/
|
||||
class CustomAuthError extends AuthError {
|
||||
constructor(message, name, status, code) {
|
||||
super(message, status, code);
|
||||
this.name = name;
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
exports.CustomAuthError = CustomAuthError;
|
||||
/**
|
||||
* Error thrown when an operation requires a session but none is present.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { AuthSessionMissingError } from '@supabase/auth-js'
|
||||
*
|
||||
* throw new AuthSessionMissingError()
|
||||
* ```
|
||||
*/
|
||||
class AuthSessionMissingError extends CustomAuthError {
|
||||
constructor() {
|
||||
super('Auth session missing!', 'AuthSessionMissingError', 400, undefined);
|
||||
}
|
||||
}
|
||||
exports.AuthSessionMissingError = AuthSessionMissingError;
|
||||
function isAuthSessionMissingError(error) {
|
||||
return isAuthError(error) && error.name === 'AuthSessionMissingError';
|
||||
}
|
||||
/**
|
||||
* Error thrown when the token response is malformed.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { AuthInvalidTokenResponseError } from '@supabase/auth-js'
|
||||
*
|
||||
* throw new AuthInvalidTokenResponseError()
|
||||
* ```
|
||||
*/
|
||||
class AuthInvalidTokenResponseError extends CustomAuthError {
|
||||
constructor() {
|
||||
super('Auth session or user missing', 'AuthInvalidTokenResponseError', 500, undefined);
|
||||
}
|
||||
}
|
||||
exports.AuthInvalidTokenResponseError = AuthInvalidTokenResponseError;
|
||||
/**
|
||||
* Error thrown when email/password credentials are invalid.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { AuthInvalidCredentialsError } from '@supabase/auth-js'
|
||||
*
|
||||
* throw new AuthInvalidCredentialsError('Email or password is incorrect')
|
||||
* ```
|
||||
*/
|
||||
class AuthInvalidCredentialsError extends CustomAuthError {
|
||||
constructor(message) {
|
||||
super(message, 'AuthInvalidCredentialsError', 400, undefined);
|
||||
}
|
||||
}
|
||||
exports.AuthInvalidCredentialsError = AuthInvalidCredentialsError;
|
||||
/**
|
||||
* Error thrown when implicit grant redirects contain an error.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { AuthImplicitGrantRedirectError } from '@supabase/auth-js'
|
||||
*
|
||||
* throw new AuthImplicitGrantRedirectError('OAuth redirect failed', {
|
||||
* error: 'access_denied',
|
||||
* code: 'oauth_error',
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
class AuthImplicitGrantRedirectError extends CustomAuthError {
|
||||
constructor(message, details = null) {
|
||||
super(message, 'AuthImplicitGrantRedirectError', 500, undefined);
|
||||
this.details = null;
|
||||
this.details = details;
|
||||
}
|
||||
toJSON() {
|
||||
return {
|
||||
name: this.name,
|
||||
message: this.message,
|
||||
status: this.status,
|
||||
details: this.details,
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.AuthImplicitGrantRedirectError = AuthImplicitGrantRedirectError;
|
||||
function isAuthImplicitGrantRedirectError(error) {
|
||||
return isAuthError(error) && error.name === 'AuthImplicitGrantRedirectError';
|
||||
}
|
||||
/**
|
||||
* Error thrown during PKCE code exchanges.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { AuthPKCEGrantCodeExchangeError } from '@supabase/auth-js'
|
||||
*
|
||||
* throw new AuthPKCEGrantCodeExchangeError('PKCE exchange failed')
|
||||
* ```
|
||||
*/
|
||||
class AuthPKCEGrantCodeExchangeError extends CustomAuthError {
|
||||
constructor(message, details = null) {
|
||||
super(message, 'AuthPKCEGrantCodeExchangeError', 500, undefined);
|
||||
this.details = null;
|
||||
this.details = details;
|
||||
}
|
||||
toJSON() {
|
||||
return {
|
||||
name: this.name,
|
||||
message: this.message,
|
||||
status: this.status,
|
||||
details: this.details,
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.AuthPKCEGrantCodeExchangeError = AuthPKCEGrantCodeExchangeError;
|
||||
/**
|
||||
* Error thrown when the PKCE code verifier is not found in storage.
|
||||
* This typically happens when the auth flow was initiated in a different
|
||||
* browser, device, or the storage was cleared.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { AuthPKCECodeVerifierMissingError } from '@supabase/auth-js'
|
||||
*
|
||||
* throw new AuthPKCECodeVerifierMissingError()
|
||||
* ```
|
||||
*/
|
||||
class AuthPKCECodeVerifierMissingError extends CustomAuthError {
|
||||
constructor() {
|
||||
super('PKCE code verifier not found in storage. ' +
|
||||
'This can happen if the auth flow was initiated in a different browser or device, ' +
|
||||
'or if the storage was cleared. For SSR frameworks (Next.js, SvelteKit, etc.), ' +
|
||||
'use @supabase/ssr on both the server and client to store the code verifier in cookies.', 'AuthPKCECodeVerifierMissingError', 400, 'pkce_code_verifier_not_found');
|
||||
}
|
||||
}
|
||||
exports.AuthPKCECodeVerifierMissingError = AuthPKCECodeVerifierMissingError;
|
||||
function isAuthPKCECodeVerifierMissingError(error) {
|
||||
return isAuthError(error) && error.name === 'AuthPKCECodeVerifierMissingError';
|
||||
}
|
||||
/**
|
||||
* Error thrown when a transient fetch issue occurs.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { AuthRetryableFetchError } from '@supabase/auth-js'
|
||||
*
|
||||
* throw new AuthRetryableFetchError('Service temporarily unavailable', 503)
|
||||
* ```
|
||||
*/
|
||||
class AuthRetryableFetchError extends CustomAuthError {
|
||||
constructor(message, status) {
|
||||
super(message, 'AuthRetryableFetchError', status, undefined);
|
||||
}
|
||||
}
|
||||
exports.AuthRetryableFetchError = AuthRetryableFetchError;
|
||||
function isAuthRetryableFetchError(error) {
|
||||
return isAuthError(error) && error.name === 'AuthRetryableFetchError';
|
||||
}
|
||||
/**
|
||||
* This error is thrown on certain methods when the password used is deemed
|
||||
* weak. Inspect the reasons to identify what password strength rules are
|
||||
* inadequate.
|
||||
*/
|
||||
/**
|
||||
* Error thrown when a supplied password is considered weak.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { AuthWeakPasswordError } from '@supabase/auth-js'
|
||||
*
|
||||
* throw new AuthWeakPasswordError('Password too short', 400, ['min_length'])
|
||||
* ```
|
||||
*/
|
||||
class AuthWeakPasswordError extends CustomAuthError {
|
||||
constructor(message, status, reasons) {
|
||||
super(message, 'AuthWeakPasswordError', status, 'weak_password');
|
||||
this.reasons = reasons;
|
||||
}
|
||||
}
|
||||
exports.AuthWeakPasswordError = AuthWeakPasswordError;
|
||||
function isAuthWeakPasswordError(error) {
|
||||
return isAuthError(error) && error.name === 'AuthWeakPasswordError';
|
||||
}
|
||||
/**
|
||||
* Error thrown when a JWT cannot be verified or parsed.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { AuthInvalidJwtError } from '@supabase/auth-js'
|
||||
*
|
||||
* throw new AuthInvalidJwtError('Token signature is invalid')
|
||||
* ```
|
||||
*/
|
||||
class AuthInvalidJwtError extends CustomAuthError {
|
||||
constructor(message) {
|
||||
super(message, 'AuthInvalidJwtError', 400, 'invalid_jwt');
|
||||
}
|
||||
}
|
||||
exports.AuthInvalidJwtError = AuthInvalidJwtError;
|
||||
//# sourceMappingURL=errors.js.map
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.handleError = handleError;
|
||||
exports._request = _request;
|
||||
exports._sessionResponse = _sessionResponse;
|
||||
exports._sessionResponsePassword = _sessionResponsePassword;
|
||||
exports._userResponse = _userResponse;
|
||||
exports._ssoResponse = _ssoResponse;
|
||||
exports._generateLinkResponse = _generateLinkResponse;
|
||||
exports._noResolveJsonResponse = _noResolveJsonResponse;
|
||||
const tslib_1 = require("tslib");
|
||||
const constants_1 = require("./constants");
|
||||
const helpers_1 = require("./helpers");
|
||||
const errors_1 = require("./errors");
|
||||
const _getErrorMessage = (err) => err.msg || err.message || err.error_description || err.error || JSON.stringify(err);
|
||||
const NETWORK_ERROR_CODES = [502, 503, 504];
|
||||
async function handleError(error) {
|
||||
var _a;
|
||||
if (!(0, helpers_1.looksLikeFetchResponse)(error)) {
|
||||
throw new errors_1.AuthRetryableFetchError(_getErrorMessage(error), 0);
|
||||
}
|
||||
if (NETWORK_ERROR_CODES.includes(error.status)) {
|
||||
// status in 500...599 range - server had an error, request might be retryed.
|
||||
throw new errors_1.AuthRetryableFetchError(_getErrorMessage(error), error.status);
|
||||
}
|
||||
let data;
|
||||
try {
|
||||
data = await error.json();
|
||||
}
|
||||
catch (e) {
|
||||
throw new errors_1.AuthUnknownError(_getErrorMessage(e), e);
|
||||
}
|
||||
let errorCode = undefined;
|
||||
const responseAPIVersion = (0, helpers_1.parseResponseAPIVersion)(error);
|
||||
if (responseAPIVersion &&
|
||||
responseAPIVersion.getTime() >= constants_1.API_VERSIONS['2024-01-01'].timestamp &&
|
||||
typeof data === 'object' &&
|
||||
data &&
|
||||
typeof data.code === 'string') {
|
||||
errorCode = data.code;
|
||||
}
|
||||
else if (typeof data === 'object' && data && typeof data.error_code === 'string') {
|
||||
errorCode = data.error_code;
|
||||
}
|
||||
if (!errorCode) {
|
||||
// Legacy support for weak password errors, when there were no error codes
|
||||
if (typeof data === 'object' &&
|
||||
data &&
|
||||
typeof data.weak_password === 'object' &&
|
||||
data.weak_password &&
|
||||
Array.isArray(data.weak_password.reasons) &&
|
||||
data.weak_password.reasons.length &&
|
||||
data.weak_password.reasons.reduce((a, i) => a && typeof i === 'string', true)) {
|
||||
throw new errors_1.AuthWeakPasswordError(_getErrorMessage(data), error.status, data.weak_password.reasons);
|
||||
}
|
||||
}
|
||||
else if (errorCode === 'weak_password') {
|
||||
throw new errors_1.AuthWeakPasswordError(_getErrorMessage(data), error.status, ((_a = data.weak_password) === null || _a === void 0 ? void 0 : _a.reasons) || []);
|
||||
}
|
||||
else if (errorCode === 'session_not_found') {
|
||||
// The `session_id` inside the JWT does not correspond to a row in the
|
||||
// `sessions` table. This usually means the user has signed out, has been
|
||||
// deleted, or their session has somehow been terminated.
|
||||
throw new errors_1.AuthSessionMissingError();
|
||||
}
|
||||
throw new errors_1.AuthApiError(_getErrorMessage(data), error.status || 500, errorCode);
|
||||
}
|
||||
const _getRequestParams = (method, options, parameters, body) => {
|
||||
const params = { method, headers: (options === null || options === void 0 ? void 0 : options.headers) || {} };
|
||||
if (method === 'GET') {
|
||||
return params;
|
||||
}
|
||||
params.headers = Object.assign({ 'Content-Type': 'application/json;charset=UTF-8' }, options === null || options === void 0 ? void 0 : options.headers);
|
||||
params.body = JSON.stringify(body);
|
||||
return Object.assign(Object.assign({}, params), parameters);
|
||||
};
|
||||
async function _request(fetcher, method, url, options) {
|
||||
var _a;
|
||||
const headers = Object.assign({}, options === null || options === void 0 ? void 0 : options.headers);
|
||||
if (!headers[constants_1.API_VERSION_HEADER_NAME]) {
|
||||
headers[constants_1.API_VERSION_HEADER_NAME] = constants_1.API_VERSIONS['2024-01-01'].name;
|
||||
}
|
||||
if (options === null || options === void 0 ? void 0 : options.jwt) {
|
||||
headers['Authorization'] = `Bearer ${options.jwt}`;
|
||||
}
|
||||
const qs = (_a = options === null || options === void 0 ? void 0 : options.query) !== null && _a !== void 0 ? _a : {};
|
||||
if (options === null || options === void 0 ? void 0 : options.redirectTo) {
|
||||
qs['redirect_to'] = options.redirectTo;
|
||||
}
|
||||
const queryString = Object.keys(qs).length ? '?' + new URLSearchParams(qs).toString() : '';
|
||||
const data = await _handleRequest(fetcher, method, url + queryString, {
|
||||
headers,
|
||||
noResolveJson: options === null || options === void 0 ? void 0 : options.noResolveJson,
|
||||
}, {}, options === null || options === void 0 ? void 0 : options.body);
|
||||
return (options === null || options === void 0 ? void 0 : options.xform) ? options === null || options === void 0 ? void 0 : options.xform(data) : { data: Object.assign({}, data), error: null };
|
||||
}
|
||||
async function _handleRequest(fetcher, method, url, options, parameters, body) {
|
||||
const requestParams = _getRequestParams(method, options, parameters, body);
|
||||
let result;
|
||||
try {
|
||||
result = await fetcher(url, Object.assign({}, requestParams));
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
// fetch failed, likely due to a network or CORS error
|
||||
throw new errors_1.AuthRetryableFetchError(_getErrorMessage(e), 0);
|
||||
}
|
||||
if (!result.ok) {
|
||||
await handleError(result);
|
||||
}
|
||||
if (options === null || options === void 0 ? void 0 : options.noResolveJson) {
|
||||
return result;
|
||||
}
|
||||
try {
|
||||
return await result.json();
|
||||
}
|
||||
catch (e) {
|
||||
await handleError(e);
|
||||
}
|
||||
}
|
||||
function _sessionResponse(data) {
|
||||
var _a;
|
||||
let session = null;
|
||||
if (hasSession(data)) {
|
||||
session = Object.assign({}, data);
|
||||
if (!data.expires_at) {
|
||||
session.expires_at = (0, helpers_1.expiresAt)(data.expires_in);
|
||||
}
|
||||
}
|
||||
const user = (_a = data.user) !== null && _a !== void 0 ? _a : data;
|
||||
return { data: { session, user }, error: null };
|
||||
}
|
||||
function _sessionResponsePassword(data) {
|
||||
const response = _sessionResponse(data);
|
||||
if (!response.error &&
|
||||
data.weak_password &&
|
||||
typeof data.weak_password === 'object' &&
|
||||
Array.isArray(data.weak_password.reasons) &&
|
||||
data.weak_password.reasons.length &&
|
||||
data.weak_password.message &&
|
||||
typeof data.weak_password.message === 'string' &&
|
||||
data.weak_password.reasons.reduce((a, i) => a && typeof i === 'string', true)) {
|
||||
response.data.weak_password = data.weak_password;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
function _userResponse(data) {
|
||||
var _a;
|
||||
const user = (_a = data.user) !== null && _a !== void 0 ? _a : data;
|
||||
return { data: { user }, error: null };
|
||||
}
|
||||
function _ssoResponse(data) {
|
||||
return { data, error: null };
|
||||
}
|
||||
function _generateLinkResponse(data) {
|
||||
const { action_link, email_otp, hashed_token, redirect_to, verification_type } = data, rest = tslib_1.__rest(data, ["action_link", "email_otp", "hashed_token", "redirect_to", "verification_type"]);
|
||||
const properties = {
|
||||
action_link,
|
||||
email_otp,
|
||||
hashed_token,
|
||||
redirect_to,
|
||||
verification_type,
|
||||
};
|
||||
const user = Object.assign({}, rest);
|
||||
return {
|
||||
data: {
|
||||
properties,
|
||||
user,
|
||||
},
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
function _noResolveJsonResponse(data) {
|
||||
return data;
|
||||
}
|
||||
/**
|
||||
* hasSession checks if the response object contains a valid session
|
||||
* @param data A response object
|
||||
* @returns true if a session is in the response
|
||||
*/
|
||||
function hasSession(data) {
|
||||
return data.access_token && data.refresh_token && data.expires_in;
|
||||
}
|
||||
//# sourceMappingURL=fetch.js.map
|
||||
+395
@@ -0,0 +1,395 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Deferred = exports.removeItemAsync = exports.getItemAsync = exports.setItemAsync = exports.looksLikeFetchResponse = exports.resolveFetch = exports.supportsLocalStorage = exports.isBrowser = void 0;
|
||||
exports.expiresAt = expiresAt;
|
||||
exports.generateCallbackId = generateCallbackId;
|
||||
exports.parseParametersFromURL = parseParametersFromURL;
|
||||
exports.decodeJWT = decodeJWT;
|
||||
exports.sleep = sleep;
|
||||
exports.retryable = retryable;
|
||||
exports.generatePKCEVerifier = generatePKCEVerifier;
|
||||
exports.generatePKCEChallenge = generatePKCEChallenge;
|
||||
exports.getCodeChallengeAndMethod = getCodeChallengeAndMethod;
|
||||
exports.parseResponseAPIVersion = parseResponseAPIVersion;
|
||||
exports.validateExp = validateExp;
|
||||
exports.getAlgorithm = getAlgorithm;
|
||||
exports.validateUUID = validateUUID;
|
||||
exports.userNotAvailableProxy = userNotAvailableProxy;
|
||||
exports.insecureUserWarningProxy = insecureUserWarningProxy;
|
||||
exports.deepClone = deepClone;
|
||||
const constants_1 = require("./constants");
|
||||
const errors_1 = require("./errors");
|
||||
const base64url_1 = require("./base64url");
|
||||
function expiresAt(expiresIn) {
|
||||
const timeNow = Math.round(Date.now() / 1000);
|
||||
return timeNow + expiresIn;
|
||||
}
|
||||
/**
|
||||
* Generates a unique identifier for internal callback subscriptions.
|
||||
*
|
||||
* This function uses JavaScript Symbols to create guaranteed-unique identifiers
|
||||
* for auth state change callbacks. Symbols are ideal for this use case because:
|
||||
* - They are guaranteed unique by the JavaScript runtime
|
||||
* - They work in all environments (browser, SSR, Node.js)
|
||||
* - They avoid issues with Next.js 16 deterministic rendering requirements
|
||||
* - They are perfect for internal, non-serializable identifiers
|
||||
*
|
||||
* Note: This function is only used for internal subscription management,
|
||||
* not for security-critical operations like session tokens.
|
||||
*/
|
||||
function generateCallbackId() {
|
||||
return Symbol('auth-callback');
|
||||
}
|
||||
const isBrowser = () => typeof window !== 'undefined' && typeof document !== 'undefined';
|
||||
exports.isBrowser = isBrowser;
|
||||
const localStorageWriteTests = {
|
||||
tested: false,
|
||||
writable: false,
|
||||
};
|
||||
/**
|
||||
* Checks whether localStorage is supported on this browser.
|
||||
*/
|
||||
const supportsLocalStorage = () => {
|
||||
if (!(0, exports.isBrowser)()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if (typeof globalThis.localStorage !== 'object') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
// DOM exception when accessing `localStorage`
|
||||
return false;
|
||||
}
|
||||
if (localStorageWriteTests.tested) {
|
||||
return localStorageWriteTests.writable;
|
||||
}
|
||||
const randomKey = `lswt-${Math.random()}${Math.random()}`;
|
||||
try {
|
||||
globalThis.localStorage.setItem(randomKey, randomKey);
|
||||
globalThis.localStorage.removeItem(randomKey);
|
||||
localStorageWriteTests.tested = true;
|
||||
localStorageWriteTests.writable = true;
|
||||
}
|
||||
catch (e) {
|
||||
// localStorage can't be written to
|
||||
// https://www.chromium.org/for-testers/bug-reporting-guidelines/uncaught-securityerror-failed-to-read-the-localstorage-property-from-window-access-is-denied-for-this-document
|
||||
localStorageWriteTests.tested = true;
|
||||
localStorageWriteTests.writable = false;
|
||||
}
|
||||
return localStorageWriteTests.writable;
|
||||
};
|
||||
exports.supportsLocalStorage = supportsLocalStorage;
|
||||
/**
|
||||
* Extracts parameters encoded in the URL both in the query and fragment.
|
||||
*/
|
||||
function parseParametersFromURL(href) {
|
||||
const result = {};
|
||||
const url = new URL(href);
|
||||
if (url.hash && url.hash[0] === '#') {
|
||||
try {
|
||||
const hashSearchParams = new URLSearchParams(url.hash.substring(1));
|
||||
hashSearchParams.forEach((value, key) => {
|
||||
result[key] = value;
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
// hash is not a query string
|
||||
}
|
||||
}
|
||||
// search parameters take precedence over hash parameters
|
||||
url.searchParams.forEach((value, key) => {
|
||||
result[key] = value;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
const resolveFetch = (customFetch) => {
|
||||
if (customFetch) {
|
||||
return (...args) => customFetch(...args);
|
||||
}
|
||||
return (...args) => fetch(...args);
|
||||
};
|
||||
exports.resolveFetch = resolveFetch;
|
||||
const looksLikeFetchResponse = (maybeResponse) => {
|
||||
return (typeof maybeResponse === 'object' &&
|
||||
maybeResponse !== null &&
|
||||
'status' in maybeResponse &&
|
||||
'ok' in maybeResponse &&
|
||||
'json' in maybeResponse &&
|
||||
typeof maybeResponse.json === 'function');
|
||||
};
|
||||
exports.looksLikeFetchResponse = looksLikeFetchResponse;
|
||||
// Storage helpers
|
||||
const setItemAsync = async (storage, key, data) => {
|
||||
await storage.setItem(key, JSON.stringify(data));
|
||||
};
|
||||
exports.setItemAsync = setItemAsync;
|
||||
const getItemAsync = async (storage, key) => {
|
||||
const value = await storage.getItem(key);
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
catch (_a) {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
exports.getItemAsync = getItemAsync;
|
||||
const removeItemAsync = async (storage, key) => {
|
||||
await storage.removeItem(key);
|
||||
};
|
||||
exports.removeItemAsync = removeItemAsync;
|
||||
/**
|
||||
* A deferred represents some asynchronous work that is not yet finished, which
|
||||
* may or may not culminate in a value.
|
||||
* Taken from: https://github.com/mike-north/types/blob/master/src/async.ts
|
||||
*/
|
||||
class Deferred {
|
||||
constructor() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-extra-semi
|
||||
;
|
||||
this.promise = new Deferred.promiseConstructor((res, rej) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-extra-semi
|
||||
;
|
||||
this.resolve = res;
|
||||
this.reject = rej;
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.Deferred = Deferred;
|
||||
Deferred.promiseConstructor = Promise;
|
||||
function decodeJWT(token) {
|
||||
const parts = token.split('.');
|
||||
if (parts.length !== 3) {
|
||||
throw new errors_1.AuthInvalidJwtError('Invalid JWT structure');
|
||||
}
|
||||
// Regex checks for base64url format
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
if (!constants_1.BASE64URL_REGEX.test(parts[i])) {
|
||||
throw new errors_1.AuthInvalidJwtError('JWT not in base64url format');
|
||||
}
|
||||
}
|
||||
const data = {
|
||||
// using base64url lib
|
||||
header: JSON.parse((0, base64url_1.stringFromBase64URL)(parts[0])),
|
||||
payload: JSON.parse((0, base64url_1.stringFromBase64URL)(parts[1])),
|
||||
signature: (0, base64url_1.base64UrlToUint8Array)(parts[2]),
|
||||
raw: {
|
||||
header: parts[0],
|
||||
payload: parts[1],
|
||||
},
|
||||
};
|
||||
return data;
|
||||
}
|
||||
/**
|
||||
* Creates a promise that resolves to null after some time.
|
||||
*/
|
||||
async function sleep(time) {
|
||||
return await new Promise((accept) => {
|
||||
setTimeout(() => accept(null), time);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Converts the provided async function into a retryable function. Each result
|
||||
* or thrown error is sent to the isRetryable function which should return true
|
||||
* if the function should run again.
|
||||
*/
|
||||
function retryable(fn, isRetryable) {
|
||||
const promise = new Promise((accept, reject) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-extra-semi
|
||||
;
|
||||
(async () => {
|
||||
for (let attempt = 0; attempt < Infinity; attempt++) {
|
||||
try {
|
||||
const result = await fn(attempt);
|
||||
if (!isRetryable(attempt, null, result)) {
|
||||
accept(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
if (!isRetryable(attempt, e)) {
|
||||
reject(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
function dec2hex(dec) {
|
||||
return ('0' + dec.toString(16)).substr(-2);
|
||||
}
|
||||
// Functions below taken from: https://stackoverflow.com/questions/63309409/creating-a-code-verifier-and-challenge-for-pkce-auth-on-spotify-api-in-reactjs
|
||||
function generatePKCEVerifier() {
|
||||
const verifierLength = 56;
|
||||
const array = new Uint32Array(verifierLength);
|
||||
if (typeof crypto === 'undefined') {
|
||||
const charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
|
||||
const charSetLen = charSet.length;
|
||||
let verifier = '';
|
||||
for (let i = 0; i < verifierLength; i++) {
|
||||
verifier += charSet.charAt(Math.floor(Math.random() * charSetLen));
|
||||
}
|
||||
return verifier;
|
||||
}
|
||||
crypto.getRandomValues(array);
|
||||
return Array.from(array, dec2hex).join('');
|
||||
}
|
||||
async function sha256(randomString) {
|
||||
const encoder = new TextEncoder();
|
||||
const encodedData = encoder.encode(randomString);
|
||||
const hash = await crypto.subtle.digest('SHA-256', encodedData);
|
||||
const bytes = new Uint8Array(hash);
|
||||
return Array.from(bytes)
|
||||
.map((c) => String.fromCharCode(c))
|
||||
.join('');
|
||||
}
|
||||
async function generatePKCEChallenge(verifier) {
|
||||
const hasCryptoSupport = typeof crypto !== 'undefined' &&
|
||||
typeof crypto.subtle !== 'undefined' &&
|
||||
typeof TextEncoder !== 'undefined';
|
||||
if (!hasCryptoSupport) {
|
||||
console.warn('WebCrypto API is not supported. Code challenge method will default to use plain instead of sha256.');
|
||||
return verifier;
|
||||
}
|
||||
const hashed = await sha256(verifier);
|
||||
return btoa(hashed).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||
}
|
||||
async function getCodeChallengeAndMethod(storage, storageKey, isPasswordRecovery = false) {
|
||||
const codeVerifier = generatePKCEVerifier();
|
||||
let storedCodeVerifier = codeVerifier;
|
||||
if (isPasswordRecovery) {
|
||||
storedCodeVerifier += '/PASSWORD_RECOVERY';
|
||||
}
|
||||
await (0, exports.setItemAsync)(storage, `${storageKey}-code-verifier`, storedCodeVerifier);
|
||||
const codeChallenge = await generatePKCEChallenge(codeVerifier);
|
||||
const codeChallengeMethod = codeVerifier === codeChallenge ? 'plain' : 's256';
|
||||
return [codeChallenge, codeChallengeMethod];
|
||||
}
|
||||
/** Parses the API version which is 2YYY-MM-DD. */
|
||||
const API_VERSION_REGEX = /^2[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|1[0-9]|2[0-9]|3[0-1])$/i;
|
||||
function parseResponseAPIVersion(response) {
|
||||
const apiVersion = response.headers.get(constants_1.API_VERSION_HEADER_NAME);
|
||||
if (!apiVersion) {
|
||||
return null;
|
||||
}
|
||||
if (!apiVersion.match(API_VERSION_REGEX)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const date = new Date(`${apiVersion}T00:00:00.0Z`);
|
||||
return date;
|
||||
}
|
||||
catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function validateExp(exp) {
|
||||
if (!exp) {
|
||||
throw new Error('Missing exp claim');
|
||||
}
|
||||
const timeNow = Math.floor(Date.now() / 1000);
|
||||
if (exp <= timeNow) {
|
||||
throw new Error('JWT has expired');
|
||||
}
|
||||
}
|
||||
function getAlgorithm(alg) {
|
||||
switch (alg) {
|
||||
case 'RS256':
|
||||
return {
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
hash: { name: 'SHA-256' },
|
||||
};
|
||||
case 'ES256':
|
||||
return {
|
||||
name: 'ECDSA',
|
||||
namedCurve: 'P-256',
|
||||
hash: { name: 'SHA-256' },
|
||||
};
|
||||
default:
|
||||
throw new Error('Invalid alg claim');
|
||||
}
|
||||
}
|
||||
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
||||
function validateUUID(str) {
|
||||
if (!UUID_REGEX.test(str)) {
|
||||
throw new Error('@supabase/auth-js: Expected parameter to be UUID but is not');
|
||||
}
|
||||
}
|
||||
function userNotAvailableProxy() {
|
||||
const proxyTarget = {};
|
||||
return new Proxy(proxyTarget, {
|
||||
get: (target, prop) => {
|
||||
if (prop === '__isUserNotAvailableProxy') {
|
||||
return true;
|
||||
}
|
||||
// Preventative check for common problematic symbols during cloning/inspection
|
||||
// These symbols might be accessed by structuredClone or other internal mechanisms.
|
||||
if (typeof prop === 'symbol') {
|
||||
const sProp = prop.toString();
|
||||
if (sProp === 'Symbol(Symbol.toPrimitive)' ||
|
||||
sProp === 'Symbol(Symbol.toStringTag)' ||
|
||||
sProp === 'Symbol(util.inspect.custom)') {
|
||||
// Node.js util.inspect
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
throw new Error(`@supabase/auth-js: client was created with userStorage option and there was no user stored in the user storage. Accessing the "${prop}" property of the session object is not supported. Please use getUser() instead.`);
|
||||
},
|
||||
set: (_target, prop) => {
|
||||
throw new Error(`@supabase/auth-js: client was created with userStorage option and there was no user stored in the user storage. Setting the "${prop}" property of the session object is not supported. Please use getUser() to fetch a user object you can manipulate.`);
|
||||
},
|
||||
deleteProperty: (_target, prop) => {
|
||||
throw new Error(`@supabase/auth-js: client was created with userStorage option and there was no user stored in the user storage. Deleting the "${prop}" property of the session object is not supported. Please use getUser() to fetch a user object you can manipulate.`);
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Creates a proxy around a user object that warns when properties are accessed on the server.
|
||||
* This is used to alert developers that using user data from getSession() on the server is insecure.
|
||||
*
|
||||
* @param user The actual user object to wrap
|
||||
* @param suppressWarningRef An object with a 'value' property that controls warning suppression
|
||||
* @returns A proxied user object that warns on property access
|
||||
*/
|
||||
function insecureUserWarningProxy(user, suppressWarningRef) {
|
||||
return new Proxy(user, {
|
||||
get: (target, prop, receiver) => {
|
||||
// Allow internal checks without warning
|
||||
if (prop === '__isInsecureUserWarningProxy') {
|
||||
return true;
|
||||
}
|
||||
// Preventative check for common problematic symbols during cloning/inspection
|
||||
// These symbols might be accessed by structuredClone or other internal mechanisms
|
||||
if (typeof prop === 'symbol') {
|
||||
const sProp = prop.toString();
|
||||
if (sProp === 'Symbol(Symbol.toPrimitive)' ||
|
||||
sProp === 'Symbol(Symbol.toStringTag)' ||
|
||||
sProp === 'Symbol(util.inspect.custom)' ||
|
||||
sProp === 'Symbol(nodejs.util.inspect.custom)') {
|
||||
// Return the actual value for these symbols to allow proper inspection
|
||||
return Reflect.get(target, prop, receiver);
|
||||
}
|
||||
}
|
||||
// Emit warning on first property access
|
||||
if (!suppressWarningRef.value && typeof prop === 'string') {
|
||||
console.warn('Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server.');
|
||||
suppressWarningRef.value = true;
|
||||
}
|
||||
return Reflect.get(target, prop, receiver);
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Deep clones a JSON-serializable object using JSON.parse(JSON.stringify(obj)).
|
||||
* Note: Only works for JSON-safe data.
|
||||
*/
|
||||
function deepClone(obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
//# sourceMappingURL=helpers.js.map
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.memoryLocalStorageAdapter = memoryLocalStorageAdapter;
|
||||
/**
|
||||
* Returns a localStorage-like object that stores the key-value pairs in
|
||||
* memory.
|
||||
*/
|
||||
function memoryLocalStorageAdapter(store = {}) {
|
||||
return {
|
||||
getItem: (key) => {
|
||||
return store[key] || null;
|
||||
},
|
||||
setItem: (key, value) => {
|
||||
store[key] = value;
|
||||
},
|
||||
removeItem: (key) => {
|
||||
delete store[key];
|
||||
},
|
||||
};
|
||||
}
|
||||
//# sourceMappingURL=local-storage.js.map
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ProcessLockAcquireTimeoutError = exports.NavigatorLockAcquireTimeoutError = exports.LockAcquireTimeoutError = exports.internals = void 0;
|
||||
exports.navigatorLock = navigatorLock;
|
||||
exports.processLock = processLock;
|
||||
const helpers_1 = require("./helpers");
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
exports.internals = {
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
debug: !!(globalThis &&
|
||||
(0, helpers_1.supportsLocalStorage)() &&
|
||||
globalThis.localStorage &&
|
||||
globalThis.localStorage.getItem('supabase.gotrue-js.locks.debug') === 'true'),
|
||||
};
|
||||
/**
|
||||
* An error thrown when a lock cannot be acquired after some amount of time.
|
||||
*
|
||||
* Use the {@link #isAcquireTimeout} property instead of checking with `instanceof`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { LockAcquireTimeoutError } from '@supabase/auth-js'
|
||||
*
|
||||
* class CustomLockError extends LockAcquireTimeoutError {
|
||||
* constructor() {
|
||||
* super('Lock timed out')
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class LockAcquireTimeoutError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.isAcquireTimeout = true;
|
||||
}
|
||||
}
|
||||
exports.LockAcquireTimeoutError = LockAcquireTimeoutError;
|
||||
/**
|
||||
* Error thrown when the browser Navigator Lock API fails to acquire a lock.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { NavigatorLockAcquireTimeoutError } from '@supabase/auth-js'
|
||||
*
|
||||
* throw new NavigatorLockAcquireTimeoutError('Lock timed out')
|
||||
* ```
|
||||
*/
|
||||
class NavigatorLockAcquireTimeoutError extends LockAcquireTimeoutError {
|
||||
}
|
||||
exports.NavigatorLockAcquireTimeoutError = NavigatorLockAcquireTimeoutError;
|
||||
/**
|
||||
* Error thrown when the process-level lock helper cannot acquire a lock.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { ProcessLockAcquireTimeoutError } from '@supabase/auth-js'
|
||||
*
|
||||
* throw new ProcessLockAcquireTimeoutError('Lock timed out')
|
||||
* ```
|
||||
*/
|
||||
class ProcessLockAcquireTimeoutError extends LockAcquireTimeoutError {
|
||||
}
|
||||
exports.ProcessLockAcquireTimeoutError = ProcessLockAcquireTimeoutError;
|
||||
/**
|
||||
* Implements a global exclusive lock using the Navigator LockManager API. It
|
||||
* is available on all browsers released after 2022-03-15 with Safari being the
|
||||
* last one to release support. If the API is not available, this function will
|
||||
* throw. Make sure you check availablility before configuring {@link
|
||||
* GoTrueClient}.
|
||||
*
|
||||
* You can turn on debugging by setting the `supabase.gotrue-js.locks.debug`
|
||||
* local storage item to `true`.
|
||||
*
|
||||
* Internals:
|
||||
*
|
||||
* Since the LockManager API does not preserve stack traces for the async
|
||||
* function passed in the `request` method, a trick is used where acquiring the
|
||||
* lock releases a previously started promise to run the operation in the `fn`
|
||||
* function. The lock waits for that promise to finish (with or without error),
|
||||
* while the function will finally wait for the result anyway.
|
||||
*
|
||||
* @param name Name of the lock to be acquired.
|
||||
* @param acquireTimeout If negative, no timeout. If 0 an error is thrown if
|
||||
* the lock can't be acquired without waiting. If positive, the lock acquire
|
||||
* will time out after so many milliseconds. An error is
|
||||
* a timeout if it has `isAcquireTimeout` set to true.
|
||||
* @param fn The operation to run once the lock is acquired.
|
||||
* @example
|
||||
* ```ts
|
||||
* await navigatorLock('sync-user', 1000, async () => {
|
||||
* await refreshSession()
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
async function navigatorLock(name, acquireTimeout, fn) {
|
||||
if (exports.internals.debug) {
|
||||
console.log('@supabase/gotrue-js: navigatorLock: acquire lock', name, acquireTimeout);
|
||||
}
|
||||
const abortController = new globalThis.AbortController();
|
||||
if (acquireTimeout > 0) {
|
||||
setTimeout(() => {
|
||||
abortController.abort();
|
||||
if (exports.internals.debug) {
|
||||
console.log('@supabase/gotrue-js: navigatorLock acquire timed out', name);
|
||||
}
|
||||
}, acquireTimeout);
|
||||
}
|
||||
// MDN article: https://developer.mozilla.org/en-US/docs/Web/API/LockManager/request
|
||||
// Wrapping navigator.locks.request() with a plain Promise is done as some
|
||||
// libraries like zone.js patch the Promise object to track the execution
|
||||
// context. However, it appears that most browsers use an internal promise
|
||||
// implementation when using the navigator.locks.request() API causing them
|
||||
// to lose context and emit confusing log messages or break certain features.
|
||||
// This wrapping is believed to help zone.js track the execution context
|
||||
// better.
|
||||
return await Promise.resolve().then(() => globalThis.navigator.locks.request(name, acquireTimeout === 0
|
||||
? {
|
||||
mode: 'exclusive',
|
||||
ifAvailable: true,
|
||||
}
|
||||
: {
|
||||
mode: 'exclusive',
|
||||
signal: abortController.signal,
|
||||
}, async (lock) => {
|
||||
if (lock) {
|
||||
if (exports.internals.debug) {
|
||||
console.log('@supabase/gotrue-js: navigatorLock: acquired', name, lock.name);
|
||||
}
|
||||
try {
|
||||
return await fn();
|
||||
}
|
||||
finally {
|
||||
if (exports.internals.debug) {
|
||||
console.log('@supabase/gotrue-js: navigatorLock: released', name, lock.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (acquireTimeout === 0) {
|
||||
if (exports.internals.debug) {
|
||||
console.log('@supabase/gotrue-js: navigatorLock: not immediately available', name);
|
||||
}
|
||||
throw new NavigatorLockAcquireTimeoutError(`Acquiring an exclusive Navigator LockManager lock "${name}" immediately failed`);
|
||||
}
|
||||
else {
|
||||
if (exports.internals.debug) {
|
||||
try {
|
||||
const result = await globalThis.navigator.locks.query();
|
||||
console.log('@supabase/gotrue-js: Navigator LockManager state', JSON.stringify(result, null, ' '));
|
||||
}
|
||||
catch (e) {
|
||||
console.warn('@supabase/gotrue-js: Error when querying Navigator LockManager state', e);
|
||||
}
|
||||
}
|
||||
// Browser is not following the Navigator LockManager spec, it
|
||||
// returned a null lock when we didn't use ifAvailable. So we can
|
||||
// pretend the lock is acquired in the name of backward compatibility
|
||||
// and user experience and just run the function.
|
||||
console.warn('@supabase/gotrue-js: Navigator LockManager returned a null lock when using #request without ifAvailable set to true, it appears this browser is not following the LockManager spec https://developer.mozilla.org/en-US/docs/Web/API/LockManager/request');
|
||||
return await fn();
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
const PROCESS_LOCKS = {};
|
||||
/**
|
||||
* Implements a global exclusive lock that works only in the current process.
|
||||
* Useful for environments like React Native or other non-browser
|
||||
* single-process (i.e. no concept of "tabs") environments.
|
||||
*
|
||||
* Use {@link #navigatorLock} in browser environments.
|
||||
*
|
||||
* @param name Name of the lock to be acquired.
|
||||
* @param acquireTimeout If negative, no timeout. If 0 an error is thrown if
|
||||
* the lock can't be acquired without waiting. If positive, the lock acquire
|
||||
* will time out after so many milliseconds. An error is
|
||||
* a timeout if it has `isAcquireTimeout` set to true.
|
||||
* @param fn The operation to run once the lock is acquired.
|
||||
* @example
|
||||
* ```ts
|
||||
* await processLock('migrate', 5000, async () => {
|
||||
* await runMigration()
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
async function processLock(name, acquireTimeout, fn) {
|
||||
var _a;
|
||||
const previousOperation = (_a = PROCESS_LOCKS[name]) !== null && _a !== void 0 ? _a : Promise.resolve();
|
||||
const currentOperation = Promise.race([
|
||||
previousOperation.catch(() => {
|
||||
// ignore error of previous operation that we're waiting to finish
|
||||
return null;
|
||||
}),
|
||||
acquireTimeout >= 0
|
||||
? new Promise((_, reject) => {
|
||||
setTimeout(() => {
|
||||
console.warn(`@supabase/gotrue-js: Lock "${name}" acquisition timed out after ${acquireTimeout}ms. ` +
|
||||
'This may be caused by another operation holding the lock. ' +
|
||||
'Consider increasing lockAcquireTimeout or checking for stuck operations.');
|
||||
reject(new ProcessLockAcquireTimeoutError(`Acquiring process lock with name "${name}" timed out`));
|
||||
}, acquireTimeout);
|
||||
})
|
||||
: null,
|
||||
].filter((x) => x))
|
||||
.catch((e) => {
|
||||
if (e && e.isAcquireTimeout) {
|
||||
throw e;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.then(async () => {
|
||||
// previous operations finished and we didn't get a race on the acquire
|
||||
// timeout, so the current operation can finally start
|
||||
return await fn();
|
||||
});
|
||||
PROCESS_LOCKS[name] = currentOperation.catch(async (e) => {
|
||||
if (e && e.isAcquireTimeout) {
|
||||
// if the current operation timed out, it doesn't mean that the previous
|
||||
// operation finished, so we need contnue waiting for it to finish
|
||||
await previousOperation;
|
||||
return null;
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
// finally wait for the current operation to finish successfully, with an
|
||||
// error or with an acquire timeout error
|
||||
return await currentOperation;
|
||||
}
|
||||
//# sourceMappingURL=locks.js.map
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.polyfillGlobalThis = polyfillGlobalThis;
|
||||
/**
|
||||
* https://mathiasbynens.be/notes/globalthis
|
||||
*/
|
||||
function polyfillGlobalThis() {
|
||||
if (typeof globalThis === 'object')
|
||||
return;
|
||||
try {
|
||||
Object.defineProperty(Object.prototype, '__magic__', {
|
||||
get: function () {
|
||||
return this;
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
// @ts-expect-error 'Allow access to magic'
|
||||
__magic__.globalThis = __magic__;
|
||||
// @ts-expect-error 'Allow access to magic'
|
||||
delete Object.prototype.__magic__;
|
||||
}
|
||||
catch (e) {
|
||||
if (typeof self !== 'undefined') {
|
||||
// @ts-expect-error 'Allow access to globals'
|
||||
self.globalThis = self;
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=polyfills.js.map
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.SIGN_OUT_SCOPES = void 0;
|
||||
const WeakPasswordReasons = ['length', 'characters', 'pwned'];
|
||||
const AMRMethods = [
|
||||
'password',
|
||||
'otp',
|
||||
'oauth',
|
||||
'totp',
|
||||
'mfa/totp',
|
||||
'mfa/phone',
|
||||
'mfa/webauthn',
|
||||
'anonymous',
|
||||
'sso/saml',
|
||||
'magiclink',
|
||||
'web3',
|
||||
'oauth_provider/authorization_code',
|
||||
];
|
||||
const FactorTypes = ['totp', 'phone', 'webauthn'];
|
||||
const FactorVerificationStatuses = ['verified', 'unverified'];
|
||||
const MFATOTPChannels = ['sms', 'whatsapp'];
|
||||
exports.SIGN_OUT_SCOPES = ['global', 'local', 'others'];
|
||||
//# sourceMappingURL=types.js.map
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.version = void 0;
|
||||
// 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
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
"use strict";
|
||||
// types and functions copied over from viem so this library doesn't depend on it
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getAddress = getAddress;
|
||||
exports.fromHex = fromHex;
|
||||
exports.toHex = toHex;
|
||||
exports.createSiweMessage = createSiweMessage;
|
||||
function getAddress(address) {
|
||||
if (!/^0x[a-fA-F0-9]{40}$/.test(address)) {
|
||||
throw new Error(`@supabase/auth-js: Address "${address}" is invalid.`);
|
||||
}
|
||||
return address.toLowerCase();
|
||||
}
|
||||
function fromHex(hex) {
|
||||
return parseInt(hex, 16);
|
||||
}
|
||||
function toHex(value) {
|
||||
const bytes = new TextEncoder().encode(value);
|
||||
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
||||
return ('0x' + hex);
|
||||
}
|
||||
/**
|
||||
* Creates EIP-4361 formatted message.
|
||||
*/
|
||||
function createSiweMessage(parameters) {
|
||||
var _a;
|
||||
const { chainId, domain, expirationTime, issuedAt = new Date(), nonce, notBefore, requestId, resources, scheme, uri, version, } = parameters;
|
||||
// Validate fields
|
||||
{
|
||||
if (!Number.isInteger(chainId))
|
||||
throw new Error(`@supabase/auth-js: Invalid SIWE message field "chainId". Chain ID must be a EIP-155 chain ID. Provided value: ${chainId}`);
|
||||
if (!domain)
|
||||
throw new Error(`@supabase/auth-js: Invalid SIWE message field "domain". Domain must be provided.`);
|
||||
if (nonce && nonce.length < 8)
|
||||
throw new Error(`@supabase/auth-js: Invalid SIWE message field "nonce". Nonce must be at least 8 characters. Provided value: ${nonce}`);
|
||||
if (!uri)
|
||||
throw new Error(`@supabase/auth-js: Invalid SIWE message field "uri". URI must be provided.`);
|
||||
if (version !== '1')
|
||||
throw new Error(`@supabase/auth-js: Invalid SIWE message field "version". Version must be '1'. Provided value: ${version}`);
|
||||
if ((_a = parameters.statement) === null || _a === void 0 ? void 0 : _a.includes('\n'))
|
||||
throw new Error(`@supabase/auth-js: Invalid SIWE message field "statement". Statement must not include '\\n'. Provided value: ${parameters.statement}`);
|
||||
}
|
||||
// Construct message
|
||||
const address = getAddress(parameters.address);
|
||||
const origin = scheme ? `${scheme}://${domain}` : domain;
|
||||
const statement = parameters.statement ? `${parameters.statement}\n` : '';
|
||||
const prefix = `${origin} wants you to sign in with your Ethereum account:\n${address}\n\n${statement}`;
|
||||
let suffix = `URI: ${uri}\nVersion: ${version}\nChain ID: ${chainId}${nonce ? `\nNonce: ${nonce}` : ''}\nIssued At: ${issuedAt.toISOString()}`;
|
||||
if (expirationTime)
|
||||
suffix += `\nExpiration Time: ${expirationTime.toISOString()}`;
|
||||
if (notBefore)
|
||||
suffix += `\nNot Before: ${notBefore.toISOString()}`;
|
||||
if (requestId)
|
||||
suffix += `\nRequest ID: ${requestId}`;
|
||||
if (resources) {
|
||||
let content = '\nResources:';
|
||||
for (const resource of resources) {
|
||||
if (!resource || typeof resource !== 'string')
|
||||
throw new Error(`@supabase/auth-js: Invalid SIWE message field "resources". Every resource must be a valid string. Provided value: ${resource}`);
|
||||
content += `\n- ${resource}`;
|
||||
}
|
||||
suffix += content;
|
||||
}
|
||||
return `${prefix}\n${suffix}`;
|
||||
}
|
||||
//# sourceMappingURL=ethereum.js.map
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
"use strict";
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.WebAuthnUnknownError = exports.WebAuthnError = void 0;
|
||||
exports.isWebAuthnError = isWebAuthnError;
|
||||
exports.identifyRegistrationError = identifyRegistrationError;
|
||||
exports.identifyAuthenticationError = identifyAuthenticationError;
|
||||
const webauthn_1 = require("./webauthn");
|
||||
/**
|
||||
* A custom Error used to return a more nuanced error detailing _why_ one of the eight documented
|
||||
* errors in the spec was raised after calling `navigator.credentials.create()` or
|
||||
* `navigator.credentials.get()`:
|
||||
*
|
||||
* - `AbortError`
|
||||
* - `ConstraintError`
|
||||
* - `InvalidStateError`
|
||||
* - `NotAllowedError`
|
||||
* - `NotSupportedError`
|
||||
* - `SecurityError`
|
||||
* - `TypeError`
|
||||
* - `UnknownError`
|
||||
*
|
||||
* Error messages were determined through investigation of the spec to determine under which
|
||||
* scenarios a given error would be raised.
|
||||
*/
|
||||
class WebAuthnError extends Error {
|
||||
constructor({ message, code, cause, name, }) {
|
||||
var _a;
|
||||
// @ts-ignore: help Rollup understand that `cause` is okay to set
|
||||
super(message, { cause });
|
||||
this.__isWebAuthnError = true;
|
||||
this.name = (_a = name !== null && name !== void 0 ? name : (cause instanceof Error ? cause.name : undefined)) !== null && _a !== void 0 ? _a : 'Unknown Error';
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
exports.WebAuthnError = WebAuthnError;
|
||||
/**
|
||||
* Error class for unknown WebAuthn errors.
|
||||
* Wraps unexpected errors that don't match known WebAuthn error conditions.
|
||||
*/
|
||||
class WebAuthnUnknownError extends WebAuthnError {
|
||||
constructor(message, originalError) {
|
||||
super({
|
||||
code: 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY',
|
||||
cause: originalError,
|
||||
message,
|
||||
});
|
||||
this.name = 'WebAuthnUnknownError';
|
||||
this.originalError = originalError;
|
||||
}
|
||||
}
|
||||
exports.WebAuthnUnknownError = WebAuthnUnknownError;
|
||||
/**
|
||||
* Type guard to check if an error is a WebAuthnError.
|
||||
* @param {unknown} error - The error to check
|
||||
* @returns {boolean} True if the error is a WebAuthnError
|
||||
*/
|
||||
function isWebAuthnError(error) {
|
||||
return typeof error === 'object' && error !== null && '__isWebAuthnError' in error;
|
||||
}
|
||||
/**
|
||||
* Attempt to intuit _why_ an error was raised after calling `navigator.credentials.create()`.
|
||||
* Maps browser errors to specific WebAuthn error codes for better debugging.
|
||||
* @param {Object} params - Error identification parameters
|
||||
* @param {Error} params.error - The error thrown by the browser
|
||||
* @param {CredentialCreationOptions} params.options - The options passed to credentials.create()
|
||||
* @returns {WebAuthnError} A WebAuthnError with a specific error code
|
||||
* @see {@link https://w3c.github.io/webauthn/#sctn-createCredential W3C WebAuthn Spec - Create Credential}
|
||||
*/
|
||||
function identifyRegistrationError({ error, options, }) {
|
||||
var _a, _b, _c;
|
||||
const { publicKey } = options;
|
||||
if (!publicKey) {
|
||||
throw Error('options was missing required publicKey property');
|
||||
}
|
||||
if (error.name === 'AbortError') {
|
||||
if (options.signal instanceof AbortSignal) {
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 16)
|
||||
return new WebAuthnError({
|
||||
message: 'Registration ceremony was sent an abort signal',
|
||||
code: 'ERROR_CEREMONY_ABORTED',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (error.name === 'ConstraintError') {
|
||||
if (((_a = publicKey.authenticatorSelection) === null || _a === void 0 ? void 0 : _a.requireResidentKey) === true) {
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-op-make-cred (Step 4)
|
||||
return new WebAuthnError({
|
||||
message: 'Discoverable credentials were required but no available authenticator supported it',
|
||||
code: 'ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
else if (
|
||||
// @ts-ignore: `mediation` doesn't yet exist on CredentialCreationOptions but it's possible as of Sept 2024
|
||||
options.mediation === 'conditional' &&
|
||||
((_b = publicKey.authenticatorSelection) === null || _b === void 0 ? void 0 : _b.userVerification) === 'required') {
|
||||
// https://w3c.github.io/webauthn/#sctn-createCredential (Step 22.4)
|
||||
return new WebAuthnError({
|
||||
message: 'User verification was required during automatic registration but it could not be performed',
|
||||
code: 'ERROR_AUTO_REGISTER_USER_VERIFICATION_FAILURE',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
else if (((_c = publicKey.authenticatorSelection) === null || _c === void 0 ? void 0 : _c.userVerification) === 'required') {
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-op-make-cred (Step 5)
|
||||
return new WebAuthnError({
|
||||
message: 'User verification was required but no available authenticator supported it',
|
||||
code: 'ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (error.name === 'InvalidStateError') {
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 20)
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-op-make-cred (Step 3)
|
||||
return new WebAuthnError({
|
||||
message: 'The authenticator was previously registered',
|
||||
code: 'ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
else if (error.name === 'NotAllowedError') {
|
||||
/**
|
||||
* Pass the error directly through. Platforms are overloading this error beyond what the spec
|
||||
* defines and we don't want to overwrite potentially useful error messages.
|
||||
*/
|
||||
return new WebAuthnError({
|
||||
message: error.message,
|
||||
code: 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
else if (error.name === 'NotSupportedError') {
|
||||
const validPubKeyCredParams = publicKey.pubKeyCredParams.filter((param) => param.type === 'public-key');
|
||||
if (validPubKeyCredParams.length === 0) {
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 10)
|
||||
return new WebAuthnError({
|
||||
message: 'No entry in pubKeyCredParams was of type "public-key"',
|
||||
code: 'ERROR_MALFORMED_PUBKEYCREDPARAMS',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-op-make-cred (Step 2)
|
||||
return new WebAuthnError({
|
||||
message: 'No available authenticator supported any of the specified pubKeyCredParams algorithms',
|
||||
code: 'ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
else if (error.name === 'SecurityError') {
|
||||
const effectiveDomain = window.location.hostname;
|
||||
if (!(0, webauthn_1.isValidDomain)(effectiveDomain)) {
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 7)
|
||||
return new WebAuthnError({
|
||||
message: `${window.location.hostname} is an invalid domain`,
|
||||
code: 'ERROR_INVALID_DOMAIN',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
else if (publicKey.rp.id !== effectiveDomain) {
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 8)
|
||||
return new WebAuthnError({
|
||||
message: `The RP ID "${publicKey.rp.id}" is invalid for this domain`,
|
||||
code: 'ERROR_INVALID_RP_ID',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (error.name === 'TypeError') {
|
||||
if (publicKey.user.id.byteLength < 1 || publicKey.user.id.byteLength > 64) {
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 5)
|
||||
return new WebAuthnError({
|
||||
message: 'User ID was not between 1 and 64 characters',
|
||||
code: 'ERROR_INVALID_USER_ID_LENGTH',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (error.name === 'UnknownError') {
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-op-make-cred (Step 1)
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-op-make-cred (Step 8)
|
||||
return new WebAuthnError({
|
||||
message: 'The authenticator was unable to process the specified options, or could not create a new credential',
|
||||
code: 'ERROR_AUTHENTICATOR_GENERAL_ERROR',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
return new WebAuthnError({
|
||||
message: 'a Non-Webauthn related error has occurred',
|
||||
code: 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Attempt to intuit _why_ an error was raised after calling `navigator.credentials.get()`.
|
||||
* Maps browser errors to specific WebAuthn error codes for better debugging.
|
||||
* @param {Object} params - Error identification parameters
|
||||
* @param {Error} params.error - The error thrown by the browser
|
||||
* @param {CredentialRequestOptions} params.options - The options passed to credentials.get()
|
||||
* @returns {WebAuthnError} A WebAuthnError with a specific error code
|
||||
* @see {@link https://w3c.github.io/webauthn/#sctn-getAssertion W3C WebAuthn Spec - Get Assertion}
|
||||
*/
|
||||
function identifyAuthenticationError({ error, options, }) {
|
||||
const { publicKey } = options;
|
||||
if (!publicKey) {
|
||||
throw Error('options was missing required publicKey property');
|
||||
}
|
||||
if (error.name === 'AbortError') {
|
||||
if (options.signal instanceof AbortSignal) {
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 16)
|
||||
return new WebAuthnError({
|
||||
message: 'Authentication ceremony was sent an abort signal',
|
||||
code: 'ERROR_CEREMONY_ABORTED',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (error.name === 'NotAllowedError') {
|
||||
/**
|
||||
* Pass the error directly through. Platforms are overloading this error beyond what the spec
|
||||
* defines and we don't want to overwrite potentially useful error messages.
|
||||
*/
|
||||
return new WebAuthnError({
|
||||
message: error.message,
|
||||
code: 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
else if (error.name === 'SecurityError') {
|
||||
const effectiveDomain = window.location.hostname;
|
||||
if (!(0, webauthn_1.isValidDomain)(effectiveDomain)) {
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-discover-from-external-source (Step 5)
|
||||
return new WebAuthnError({
|
||||
message: `${window.location.hostname} is an invalid domain`,
|
||||
code: 'ERROR_INVALID_DOMAIN',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
else if (publicKey.rpId !== effectiveDomain) {
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-discover-from-external-source (Step 6)
|
||||
return new WebAuthnError({
|
||||
message: `The RP ID "${publicKey.rpId}" is invalid for this domain`,
|
||||
code: 'ERROR_INVALID_RP_ID',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (error.name === 'UnknownError') {
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-op-get-assertion (Step 1)
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-op-get-assertion (Step 12)
|
||||
return new WebAuthnError({
|
||||
message: 'The authenticator was unable to process the specified options, or could not create a new assertion signature',
|
||||
code: 'ERROR_AUTHENTICATOR_GENERAL_ERROR',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
return new WebAuthnError({
|
||||
message: 'a Non-Webauthn related error has occurred',
|
||||
code: 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=webauthn.errors.js.map
|
||||
+706
@@ -0,0 +1,706 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.WebAuthnApi = exports.DEFAULT_REQUEST_OPTIONS = exports.DEFAULT_CREATION_OPTIONS = exports.webAuthnAbortService = exports.WebAuthnAbortService = exports.identifyAuthenticationError = exports.identifyRegistrationError = exports.isWebAuthnError = exports.WebAuthnError = void 0;
|
||||
exports.deserializeCredentialCreationOptions = deserializeCredentialCreationOptions;
|
||||
exports.deserializeCredentialRequestOptions = deserializeCredentialRequestOptions;
|
||||
exports.serializeCredentialCreationResponse = serializeCredentialCreationResponse;
|
||||
exports.serializeCredentialRequestResponse = serializeCredentialRequestResponse;
|
||||
exports.isValidDomain = isValidDomain;
|
||||
exports.createCredential = createCredential;
|
||||
exports.getCredential = getCredential;
|
||||
exports.mergeCredentialCreationOptions = mergeCredentialCreationOptions;
|
||||
exports.mergeCredentialRequestOptions = mergeCredentialRequestOptions;
|
||||
const tslib_1 = require("tslib");
|
||||
const base64url_1 = require("./base64url");
|
||||
const errors_1 = require("./errors");
|
||||
const helpers_1 = require("./helpers");
|
||||
const webauthn_errors_1 = require("./webauthn.errors");
|
||||
Object.defineProperty(exports, "identifyAuthenticationError", { enumerable: true, get: function () { return webauthn_errors_1.identifyAuthenticationError; } });
|
||||
Object.defineProperty(exports, "identifyRegistrationError", { enumerable: true, get: function () { return webauthn_errors_1.identifyRegistrationError; } });
|
||||
Object.defineProperty(exports, "isWebAuthnError", { enumerable: true, get: function () { return webauthn_errors_1.isWebAuthnError; } });
|
||||
Object.defineProperty(exports, "WebAuthnError", { enumerable: true, get: function () { return webauthn_errors_1.WebAuthnError; } });
|
||||
/**
|
||||
* WebAuthn abort service to manage ceremony cancellation.
|
||||
* Ensures only one WebAuthn ceremony is active at a time to prevent "operation already in progress" errors.
|
||||
*
|
||||
* @experimental This class is experimental and may change in future releases
|
||||
* @see {@link https://w3c.github.io/webauthn/#sctn-automation-webdriver-capability W3C WebAuthn Spec - Aborting Ceremonies}
|
||||
*/
|
||||
class WebAuthnAbortService {
|
||||
/**
|
||||
* Create an abort signal for a new WebAuthn operation.
|
||||
* Automatically cancels any existing operation.
|
||||
*
|
||||
* @returns {AbortSignal} Signal to pass to navigator.credentials.create() or .get()
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal MDN - AbortSignal}
|
||||
*/
|
||||
createNewAbortSignal() {
|
||||
// Abort any existing calls to navigator.credentials.create() or navigator.credentials.get()
|
||||
if (this.controller) {
|
||||
const abortError = new Error('Cancelling existing WebAuthn API call for new one');
|
||||
abortError.name = 'AbortError';
|
||||
this.controller.abort(abortError);
|
||||
}
|
||||
const newController = new AbortController();
|
||||
this.controller = newController;
|
||||
return newController.signal;
|
||||
}
|
||||
/**
|
||||
* Manually cancel the current WebAuthn operation.
|
||||
* Useful for cleaning up when user cancels or navigates away.
|
||||
*
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort MDN - AbortController.abort}
|
||||
*/
|
||||
cancelCeremony() {
|
||||
if (this.controller) {
|
||||
const abortError = new Error('Manually cancelling existing WebAuthn API call');
|
||||
abortError.name = 'AbortError';
|
||||
this.controller.abort(abortError);
|
||||
this.controller = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.WebAuthnAbortService = WebAuthnAbortService;
|
||||
/**
|
||||
* Singleton instance to ensure only one WebAuthn ceremony is active at a time.
|
||||
* This prevents "operation already in progress" errors when retrying WebAuthn operations.
|
||||
*
|
||||
* @experimental This instance is experimental and may change in future releases
|
||||
*/
|
||||
exports.webAuthnAbortService = new WebAuthnAbortService();
|
||||
/**
|
||||
* Convert base64url encoded strings in WebAuthn credential creation options to ArrayBuffers
|
||||
* as required by the WebAuthn browser API.
|
||||
* Supports both native WebAuthn Level 3 parseCreationOptionsFromJSON and manual fallback.
|
||||
*
|
||||
* @param {ServerCredentialCreationOptions} options - JSON options from server with base64url encoded fields
|
||||
* @returns {PublicKeyCredentialCreationOptionsFuture} Options ready for navigator.credentials.create()
|
||||
* @see {@link https://w3c.github.io/webauthn/#sctn-parseCreationOptionsFromJSON W3C WebAuthn Spec - parseCreationOptionsFromJSON}
|
||||
*/
|
||||
function deserializeCredentialCreationOptions(options) {
|
||||
if (!options) {
|
||||
throw new Error('Credential creation options are required');
|
||||
}
|
||||
// Check if the native parseCreationOptionsFromJSON method is available
|
||||
if (typeof PublicKeyCredential !== 'undefined' &&
|
||||
'parseCreationOptionsFromJSON' in PublicKeyCredential &&
|
||||
typeof PublicKeyCredential
|
||||
.parseCreationOptionsFromJSON === 'function') {
|
||||
// Use the native WebAuthn Level 3 method
|
||||
return PublicKeyCredential.parseCreationOptionsFromJSON(
|
||||
/** we assert the options here as typescript still doesn't know about future webauthn types */
|
||||
options);
|
||||
}
|
||||
// Fallback to manual parsing for browsers that don't support the native method
|
||||
// Destructure to separate fields that need transformation
|
||||
const { challenge: challengeStr, user: userOpts, excludeCredentials } = options, restOptions = tslib_1.__rest(options
|
||||
// Convert challenge from base64url to ArrayBuffer
|
||||
, ["challenge", "user", "excludeCredentials"]);
|
||||
// Convert challenge from base64url to ArrayBuffer
|
||||
const challenge = (0, base64url_1.base64UrlToUint8Array)(challengeStr).buffer;
|
||||
// Convert user.id from base64url to ArrayBuffer
|
||||
const user = Object.assign(Object.assign({}, userOpts), { id: (0, base64url_1.base64UrlToUint8Array)(userOpts.id).buffer });
|
||||
// Build the result object
|
||||
const result = Object.assign(Object.assign({}, restOptions), { challenge,
|
||||
user });
|
||||
// Only add excludeCredentials if it exists
|
||||
if (excludeCredentials && excludeCredentials.length > 0) {
|
||||
result.excludeCredentials = new Array(excludeCredentials.length);
|
||||
for (let i = 0; i < excludeCredentials.length; i++) {
|
||||
const cred = excludeCredentials[i];
|
||||
result.excludeCredentials[i] = Object.assign(Object.assign({}, cred), { id: (0, base64url_1.base64UrlToUint8Array)(cred.id).buffer, type: cred.type || 'public-key',
|
||||
// Cast transports to handle future transport types like "cable"
|
||||
transports: cred.transports });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Convert base64url encoded strings in WebAuthn credential request options to ArrayBuffers
|
||||
* as required by the WebAuthn browser API.
|
||||
* Supports both native WebAuthn Level 3 parseRequestOptionsFromJSON and manual fallback.
|
||||
*
|
||||
* @param {ServerCredentialRequestOptions} options - JSON options from server with base64url encoded fields
|
||||
* @returns {PublicKeyCredentialRequestOptionsFuture} Options ready for navigator.credentials.get()
|
||||
* @see {@link https://w3c.github.io/webauthn/#sctn-parseRequestOptionsFromJSON W3C WebAuthn Spec - parseRequestOptionsFromJSON}
|
||||
*/
|
||||
function deserializeCredentialRequestOptions(options) {
|
||||
if (!options) {
|
||||
throw new Error('Credential request options are required');
|
||||
}
|
||||
// Check if the native parseRequestOptionsFromJSON method is available
|
||||
if (typeof PublicKeyCredential !== 'undefined' &&
|
||||
'parseRequestOptionsFromJSON' in PublicKeyCredential &&
|
||||
typeof PublicKeyCredential
|
||||
.parseRequestOptionsFromJSON === 'function') {
|
||||
// Use the native WebAuthn Level 3 method
|
||||
return PublicKeyCredential.parseRequestOptionsFromJSON(options);
|
||||
}
|
||||
// Fallback to manual parsing for browsers that don't support the native method
|
||||
// Destructure to separate fields that need transformation
|
||||
const { challenge: challengeStr, allowCredentials } = options, restOptions = tslib_1.__rest(options
|
||||
// Convert challenge from base64url to ArrayBuffer
|
||||
, ["challenge", "allowCredentials"]);
|
||||
// Convert challenge from base64url to ArrayBuffer
|
||||
const challenge = (0, base64url_1.base64UrlToUint8Array)(challengeStr).buffer;
|
||||
// Build the result object
|
||||
const result = Object.assign(Object.assign({}, restOptions), { challenge });
|
||||
// Only add allowCredentials if it exists
|
||||
if (allowCredentials && allowCredentials.length > 0) {
|
||||
result.allowCredentials = new Array(allowCredentials.length);
|
||||
for (let i = 0; i < allowCredentials.length; i++) {
|
||||
const cred = allowCredentials[i];
|
||||
result.allowCredentials[i] = Object.assign(Object.assign({}, cred), { id: (0, base64url_1.base64UrlToUint8Array)(cred.id).buffer, type: cred.type || 'public-key',
|
||||
// Cast transports to handle future transport types like "cable"
|
||||
transports: cred.transports });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Convert a registration/enrollment credential response to server format.
|
||||
* Serializes binary fields to base64url for JSON transmission.
|
||||
* Supports both native WebAuthn Level 3 toJSON and manual fallback.
|
||||
*
|
||||
* @param {RegistrationCredential} credential - Credential from navigator.credentials.create()
|
||||
* @returns {RegistrationResponseJSON} JSON-serializable credential for server
|
||||
* @see {@link https://w3c.github.io/webauthn/#dom-publickeycredential-tojson W3C WebAuthn Spec - toJSON}
|
||||
*/
|
||||
function serializeCredentialCreationResponse(credential) {
|
||||
var _a;
|
||||
// Check if the credential instance has the toJSON method
|
||||
if ('toJSON' in credential && typeof credential.toJSON === 'function') {
|
||||
// Use the native WebAuthn Level 3 method
|
||||
return credential.toJSON();
|
||||
}
|
||||
const credentialWithAttachment = credential;
|
||||
return {
|
||||
id: credential.id,
|
||||
rawId: credential.id,
|
||||
response: {
|
||||
attestationObject: (0, base64url_1.bytesToBase64URL)(new Uint8Array(credential.response.attestationObject)),
|
||||
clientDataJSON: (0, base64url_1.bytesToBase64URL)(new Uint8Array(credential.response.clientDataJSON)),
|
||||
},
|
||||
type: 'public-key',
|
||||
clientExtensionResults: credential.getClientExtensionResults(),
|
||||
// Convert null to undefined and cast to AuthenticatorAttachment type
|
||||
authenticatorAttachment: ((_a = credentialWithAttachment.authenticatorAttachment) !== null && _a !== void 0 ? _a : undefined),
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Convert an authentication/verification credential response to server format.
|
||||
* Serializes binary fields to base64url for JSON transmission.
|
||||
* Supports both native WebAuthn Level 3 toJSON and manual fallback.
|
||||
*
|
||||
* @param {AuthenticationCredential} credential - Credential from navigator.credentials.get()
|
||||
* @returns {AuthenticationResponseJSON} JSON-serializable credential for server
|
||||
* @see {@link https://w3c.github.io/webauthn/#dom-publickeycredential-tojson W3C WebAuthn Spec - toJSON}
|
||||
*/
|
||||
function serializeCredentialRequestResponse(credential) {
|
||||
var _a;
|
||||
// Check if the credential instance has the toJSON method
|
||||
if ('toJSON' in credential && typeof credential.toJSON === 'function') {
|
||||
// Use the native WebAuthn Level 3 method
|
||||
return credential.toJSON();
|
||||
}
|
||||
// Fallback to manual conversion for browsers that don't support toJSON
|
||||
// Access authenticatorAttachment via type assertion to handle TypeScript version differences
|
||||
// @simplewebauthn/types includes this property but base TypeScript 4.7.4 doesn't
|
||||
const credentialWithAttachment = credential;
|
||||
const clientExtensionResults = credential.getClientExtensionResults();
|
||||
const assertionResponse = credential.response;
|
||||
return {
|
||||
id: credential.id,
|
||||
rawId: credential.id, // W3C spec expects rawId to match id for JSON format
|
||||
response: {
|
||||
authenticatorData: (0, base64url_1.bytesToBase64URL)(new Uint8Array(assertionResponse.authenticatorData)),
|
||||
clientDataJSON: (0, base64url_1.bytesToBase64URL)(new Uint8Array(assertionResponse.clientDataJSON)),
|
||||
signature: (0, base64url_1.bytesToBase64URL)(new Uint8Array(assertionResponse.signature)),
|
||||
userHandle: assertionResponse.userHandle
|
||||
? (0, base64url_1.bytesToBase64URL)(new Uint8Array(assertionResponse.userHandle))
|
||||
: undefined,
|
||||
},
|
||||
type: 'public-key',
|
||||
clientExtensionResults,
|
||||
// Convert null to undefined and cast to AuthenticatorAttachment type
|
||||
authenticatorAttachment: ((_a = credentialWithAttachment.authenticatorAttachment) !== null && _a !== void 0 ? _a : undefined),
|
||||
};
|
||||
}
|
||||
/**
|
||||
* A simple test to determine if a hostname is a properly-formatted domain name.
|
||||
* Considers localhost valid for development environments.
|
||||
*
|
||||
* A "valid domain" is defined here: https://url.spec.whatwg.org/#valid-domain
|
||||
*
|
||||
* Regex sourced from here:
|
||||
* https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch08s15.html
|
||||
*
|
||||
* @param {string} hostname - The hostname to validate
|
||||
* @returns {boolean} True if valid domain or localhost
|
||||
* @see {@link https://url.spec.whatwg.org/#valid-domain WHATWG URL Spec - Valid Domain}
|
||||
*/
|
||||
function isValidDomain(hostname) {
|
||||
return (
|
||||
// Consider localhost valid as well since it's okay wrt Secure Contexts
|
||||
hostname === 'localhost' || /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i.test(hostname));
|
||||
}
|
||||
/**
|
||||
* Determine if the browser is capable of WebAuthn.
|
||||
* Checks for necessary Web APIs: PublicKeyCredential and Credential Management.
|
||||
*
|
||||
* @returns {boolean} True if browser supports WebAuthn
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential#browser_compatibility MDN - PublicKeyCredential Browser Compatibility}
|
||||
*/
|
||||
function browserSupportsWebAuthn() {
|
||||
var _a, _b;
|
||||
return !!((0, helpers_1.isBrowser)() &&
|
||||
'PublicKeyCredential' in window &&
|
||||
window.PublicKeyCredential &&
|
||||
'credentials' in navigator &&
|
||||
typeof ((_a = navigator === null || navigator === void 0 ? void 0 : navigator.credentials) === null || _a === void 0 ? void 0 : _a.create) === 'function' &&
|
||||
typeof ((_b = navigator === null || navigator === void 0 ? void 0 : navigator.credentials) === null || _b === void 0 ? void 0 : _b.get) === 'function');
|
||||
}
|
||||
/**
|
||||
* Create a WebAuthn credential using the browser's credentials API.
|
||||
* Wraps navigator.credentials.create() with error handling.
|
||||
*
|
||||
* @param {CredentialCreationOptions} options - Options including publicKey parameters
|
||||
* @returns {Promise<RequestResult<RegistrationCredential, WebAuthnError>>} Created credential or error
|
||||
* @see {@link https://w3c.github.io/webauthn/#sctn-createCredential W3C WebAuthn Spec - Create Credential}
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/create MDN - credentials.create}
|
||||
*/
|
||||
async function createCredential(options) {
|
||||
try {
|
||||
const response = await navigator.credentials.create(
|
||||
/** we assert the type here until typescript types are updated */
|
||||
options);
|
||||
if (!response) {
|
||||
return {
|
||||
data: null,
|
||||
error: new webauthn_errors_1.WebAuthnUnknownError('Empty credential response', response),
|
||||
};
|
||||
}
|
||||
if (!(response instanceof PublicKeyCredential)) {
|
||||
return {
|
||||
data: null,
|
||||
error: new webauthn_errors_1.WebAuthnUnknownError('Browser returned unexpected credential type', response),
|
||||
};
|
||||
}
|
||||
return { data: response, error: null };
|
||||
}
|
||||
catch (err) {
|
||||
return {
|
||||
data: null,
|
||||
error: (0, webauthn_errors_1.identifyRegistrationError)({
|
||||
error: err,
|
||||
options,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get a WebAuthn credential using the browser's credentials API.
|
||||
* Wraps navigator.credentials.get() with error handling.
|
||||
*
|
||||
* @param {CredentialRequestOptions} options - Options including publicKey parameters
|
||||
* @returns {Promise<RequestResult<AuthenticationCredential, WebAuthnError>>} Retrieved credential or error
|
||||
* @see {@link https://w3c.github.io/webauthn/#sctn-getAssertion W3C WebAuthn Spec - Get Assertion}
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/get MDN - credentials.get}
|
||||
*/
|
||||
async function getCredential(options) {
|
||||
try {
|
||||
const response = await navigator.credentials.get(
|
||||
/** we assert the type here until typescript types are updated */
|
||||
options);
|
||||
if (!response) {
|
||||
return {
|
||||
data: null,
|
||||
error: new webauthn_errors_1.WebAuthnUnknownError('Empty credential response', response),
|
||||
};
|
||||
}
|
||||
if (!(response instanceof PublicKeyCredential)) {
|
||||
return {
|
||||
data: null,
|
||||
error: new webauthn_errors_1.WebAuthnUnknownError('Browser returned unexpected credential type', response),
|
||||
};
|
||||
}
|
||||
return { data: response, error: null };
|
||||
}
|
||||
catch (err) {
|
||||
return {
|
||||
data: null,
|
||||
error: (0, webauthn_errors_1.identifyAuthenticationError)({
|
||||
error: err,
|
||||
options,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.DEFAULT_CREATION_OPTIONS = {
|
||||
hints: ['security-key'],
|
||||
authenticatorSelection: {
|
||||
authenticatorAttachment: 'cross-platform',
|
||||
requireResidentKey: false,
|
||||
/** set to preferred because older yubikeys don't have PIN/Biometric */
|
||||
userVerification: 'preferred',
|
||||
residentKey: 'discouraged',
|
||||
},
|
||||
attestation: 'direct',
|
||||
};
|
||||
exports.DEFAULT_REQUEST_OPTIONS = {
|
||||
/** set to preferred because older yubikeys don't have PIN/Biometric */
|
||||
userVerification: 'preferred',
|
||||
hints: ['security-key'],
|
||||
attestation: 'direct',
|
||||
};
|
||||
function deepMerge(...sources) {
|
||||
const isObject = (val) => val !== null && typeof val === 'object' && !Array.isArray(val);
|
||||
const isArrayBufferLike = (val) => val instanceof ArrayBuffer || ArrayBuffer.isView(val);
|
||||
const result = {};
|
||||
for (const source of sources) {
|
||||
if (!source)
|
||||
continue;
|
||||
for (const key in source) {
|
||||
const value = source[key];
|
||||
if (value === undefined)
|
||||
continue;
|
||||
if (Array.isArray(value)) {
|
||||
// preserve array reference, including unions like AuthenticatorTransport[]
|
||||
result[key] = value;
|
||||
}
|
||||
else if (isArrayBufferLike(value)) {
|
||||
result[key] = value;
|
||||
}
|
||||
else if (isObject(value)) {
|
||||
const existing = result[key];
|
||||
if (isObject(existing)) {
|
||||
result[key] = deepMerge(existing, value);
|
||||
}
|
||||
else {
|
||||
result[key] = deepMerge(value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Merges WebAuthn credential creation options with overrides.
|
||||
* Sets sensible defaults for authenticator selection and extensions.
|
||||
*
|
||||
* @param {PublicKeyCredentialCreationOptionsFuture} baseOptions - The base options from the server
|
||||
* @param {PublicKeyCredentialCreationOptionsFuture} overrides - Optional overrides to apply
|
||||
* @param {string} friendlyName - Optional friendly name for the credential
|
||||
* @returns {PublicKeyCredentialCreationOptionsFuture} Merged credential creation options
|
||||
* @see {@link https://w3c.github.io/webauthn/#dictdef-authenticatorselectioncriteria W3C WebAuthn Spec - AuthenticatorSelectionCriteria}
|
||||
*/
|
||||
function mergeCredentialCreationOptions(baseOptions, overrides) {
|
||||
return deepMerge(exports.DEFAULT_CREATION_OPTIONS, baseOptions, overrides || {});
|
||||
}
|
||||
/**
|
||||
* Merges WebAuthn credential request options with overrides.
|
||||
* Sets sensible defaults for user verification and hints.
|
||||
*
|
||||
* @param {PublicKeyCredentialRequestOptionsFuture} baseOptions - The base options from the server
|
||||
* @param {PublicKeyCredentialRequestOptionsFuture} overrides - Optional overrides to apply
|
||||
* @returns {PublicKeyCredentialRequestOptionsFuture} Merged credential request options
|
||||
* @see {@link https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptions W3C WebAuthn Spec - PublicKeyCredentialRequestOptions}
|
||||
*/
|
||||
function mergeCredentialRequestOptions(baseOptions, overrides) {
|
||||
return deepMerge(exports.DEFAULT_REQUEST_OPTIONS, baseOptions, overrides || {});
|
||||
}
|
||||
/**
|
||||
* WebAuthn API wrapper for Supabase Auth.
|
||||
* Provides methods for enrolling, challenging, verifying, authenticating, and registering WebAuthn credentials.
|
||||
*
|
||||
* @experimental This API is experimental and may change in future releases
|
||||
* @see {@link https://w3c.github.io/webauthn/ W3C WebAuthn Specification}
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API MDN - Web Authentication API}
|
||||
*/
|
||||
class WebAuthnApi {
|
||||
constructor(client) {
|
||||
this.client = client;
|
||||
// Bind all methods so they can be destructured
|
||||
this.enroll = this._enroll.bind(this);
|
||||
this.challenge = this._challenge.bind(this);
|
||||
this.verify = this._verify.bind(this);
|
||||
this.authenticate = this._authenticate.bind(this);
|
||||
this.register = this._register.bind(this);
|
||||
}
|
||||
/**
|
||||
* Enroll a new WebAuthn factor.
|
||||
* Creates an unverified WebAuthn factor that must be verified with a credential.
|
||||
*
|
||||
* @experimental This method is experimental and may change in future releases
|
||||
* @param {Omit<MFAEnrollWebauthnParams, 'factorType'>} params - Enrollment parameters (friendlyName required)
|
||||
* @returns {Promise<AuthMFAEnrollWebauthnResponse>} Enrolled factor details or error
|
||||
* @see {@link https://w3c.github.io/webauthn/#sctn-registering-a-new-credential W3C WebAuthn Spec - Registering a New Credential}
|
||||
*/
|
||||
async _enroll(params) {
|
||||
return this.client.mfa.enroll(Object.assign(Object.assign({}, params), { factorType: 'webauthn' }));
|
||||
}
|
||||
/**
|
||||
* Challenge for WebAuthn credential creation or authentication.
|
||||
* Combines server challenge with browser credential operations.
|
||||
* Handles both registration (create) and authentication (request) flows.
|
||||
*
|
||||
* @experimental This method is experimental and may change in future releases
|
||||
* @param {MFAChallengeWebauthnParams & { friendlyName?: string; signal?: AbortSignal }} params - Challenge parameters including factorId
|
||||
* @param {Object} overrides - Allows you to override the parameters passed to navigator.credentials
|
||||
* @param {PublicKeyCredentialCreationOptionsFuture} overrides.create - Override options for credential creation
|
||||
* @param {PublicKeyCredentialRequestOptionsFuture} overrides.request - Override options for credential request
|
||||
* @returns {Promise<RequestResult>} Challenge response with credential or error
|
||||
* @see {@link https://w3c.github.io/webauthn/#sctn-credential-creation W3C WebAuthn Spec - Credential Creation}
|
||||
* @see {@link https://w3c.github.io/webauthn/#sctn-verifying-assertion W3C WebAuthn Spec - Verifying Assertion}
|
||||
*/
|
||||
async _challenge({ factorId, webauthn, friendlyName, signal, }, overrides) {
|
||||
var _a;
|
||||
try {
|
||||
// Get challenge from server using the client's MFA methods
|
||||
const { data: challengeResponse, error: challengeError } = await this.client.mfa.challenge({
|
||||
factorId,
|
||||
webauthn,
|
||||
});
|
||||
if (!challengeResponse) {
|
||||
return { data: null, error: challengeError };
|
||||
}
|
||||
const abortSignal = signal !== null && signal !== void 0 ? signal : exports.webAuthnAbortService.createNewAbortSignal();
|
||||
/** webauthn will fail if either of the name/displayname are blank */
|
||||
if (challengeResponse.webauthn.type === 'create') {
|
||||
const { user } = challengeResponse.webauthn.credential_options.publicKey;
|
||||
if (!user.name) {
|
||||
// Preserve original format: use friendlyName if provided, otherwise fetch fallback
|
||||
// This maintains backward compatibility with the ${user.id}:${name} format
|
||||
const nameToUse = friendlyName;
|
||||
if (!nameToUse) {
|
||||
// Only fetch user data if friendlyName is not provided (bug fix for null friendlyName)
|
||||
const currentUser = await this.client.getUser();
|
||||
const userData = currentUser.data.user;
|
||||
const fallbackName = ((_a = userData === null || userData === void 0 ? void 0 : userData.user_metadata) === null || _a === void 0 ? void 0 : _a.name) || (userData === null || userData === void 0 ? void 0 : userData.email) || (userData === null || userData === void 0 ? void 0 : userData.id) || 'User';
|
||||
user.name = `${user.id}:${fallbackName}`;
|
||||
}
|
||||
else {
|
||||
user.name = `${user.id}:${nameToUse}`;
|
||||
}
|
||||
}
|
||||
if (!user.displayName) {
|
||||
user.displayName = user.name;
|
||||
}
|
||||
}
|
||||
switch (challengeResponse.webauthn.type) {
|
||||
case 'create': {
|
||||
const options = mergeCredentialCreationOptions(challengeResponse.webauthn.credential_options.publicKey, overrides === null || overrides === void 0 ? void 0 : overrides.create);
|
||||
const { data, error } = await createCredential({
|
||||
publicKey: options,
|
||||
signal: abortSignal,
|
||||
});
|
||||
if (data) {
|
||||
return {
|
||||
data: {
|
||||
factorId,
|
||||
challengeId: challengeResponse.id,
|
||||
webauthn: {
|
||||
type: challengeResponse.webauthn.type,
|
||||
credential_response: data,
|
||||
},
|
||||
},
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
return { data: null, error };
|
||||
}
|
||||
case 'request': {
|
||||
const options = mergeCredentialRequestOptions(challengeResponse.webauthn.credential_options.publicKey, overrides === null || overrides === void 0 ? void 0 : overrides.request);
|
||||
const { data, error } = await getCredential(Object.assign(Object.assign({}, challengeResponse.webauthn.credential_options), { publicKey: options, signal: abortSignal }));
|
||||
if (data) {
|
||||
return {
|
||||
data: {
|
||||
factorId,
|
||||
challengeId: challengeResponse.id,
|
||||
webauthn: {
|
||||
type: challengeResponse.webauthn.type,
|
||||
credential_response: data,
|
||||
},
|
||||
},
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
return { data: null, error };
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
if ((0, errors_1.isAuthError)(error)) {
|
||||
return { data: null, error };
|
||||
}
|
||||
return {
|
||||
data: null,
|
||||
error: new errors_1.AuthUnknownError('Unexpected error in challenge', error),
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Verify a WebAuthn credential with the server.
|
||||
* Completes the WebAuthn ceremony by sending the credential to the server for verification.
|
||||
*
|
||||
* @experimental This method is experimental and may change in future releases
|
||||
* @param {Object} params - Verification parameters
|
||||
* @param {string} params.challengeId - ID of the challenge being verified
|
||||
* @param {string} params.factorId - ID of the WebAuthn factor
|
||||
* @param {MFAVerifyWebauthnParams<T>['webauthn']} params.webauthn - WebAuthn credential response
|
||||
* @returns {Promise<AuthMFAVerifyResponse>} Verification result with session or error
|
||||
* @see {@link https://w3c.github.io/webauthn/#sctn-verifying-assertion W3C WebAuthn Spec - Verifying an Authentication Assertion}
|
||||
* */
|
||||
async _verify({ challengeId, factorId, webauthn, }) {
|
||||
return this.client.mfa.verify({
|
||||
factorId,
|
||||
challengeId,
|
||||
webauthn: webauthn,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Complete WebAuthn authentication flow.
|
||||
* Performs challenge and verification in a single operation for existing credentials.
|
||||
*
|
||||
* @experimental This method is experimental and may change in future releases
|
||||
* @param {Object} params - Authentication parameters
|
||||
* @param {string} params.factorId - ID of the WebAuthn factor to authenticate with
|
||||
* @param {Object} params.webauthn - WebAuthn configuration
|
||||
* @param {string} params.webauthn.rpId - Relying Party ID (defaults to current hostname)
|
||||
* @param {string[]} params.webauthn.rpOrigins - Allowed origins (defaults to current origin)
|
||||
* @param {AbortSignal} params.webauthn.signal - Optional abort signal
|
||||
* @param {PublicKeyCredentialRequestOptionsFuture} overrides - Override options for navigator.credentials.get
|
||||
* @returns {Promise<RequestResult<AuthMFAVerifyResponseData, WebAuthnError | AuthError>>} Authentication result
|
||||
* @see {@link https://w3c.github.io/webauthn/#sctn-authentication W3C WebAuthn Spec - Authentication Ceremony}
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredentialRequestOptions MDN - PublicKeyCredentialRequestOptions}
|
||||
*/
|
||||
async _authenticate({ factorId, webauthn: { rpId = typeof window !== 'undefined' ? window.location.hostname : undefined, rpOrigins = typeof window !== 'undefined' ? [window.location.origin] : undefined, signal, } = {}, }, overrides) {
|
||||
if (!rpId) {
|
||||
return {
|
||||
data: null,
|
||||
error: new errors_1.AuthError('rpId is required for WebAuthn authentication'),
|
||||
};
|
||||
}
|
||||
try {
|
||||
if (!browserSupportsWebAuthn()) {
|
||||
return {
|
||||
data: null,
|
||||
error: new errors_1.AuthUnknownError('Browser does not support WebAuthn', null),
|
||||
};
|
||||
}
|
||||
// Get challenge and credential
|
||||
const { data: challengeResponse, error: challengeError } = await this.challenge({
|
||||
factorId,
|
||||
webauthn: { rpId, rpOrigins },
|
||||
signal,
|
||||
}, { request: overrides });
|
||||
if (!challengeResponse) {
|
||||
return { data: null, error: challengeError };
|
||||
}
|
||||
const { webauthn } = challengeResponse;
|
||||
// Verify credential
|
||||
return this._verify({
|
||||
factorId,
|
||||
challengeId: challengeResponse.challengeId,
|
||||
webauthn: {
|
||||
type: webauthn.type,
|
||||
rpId,
|
||||
rpOrigins,
|
||||
credential_response: webauthn.credential_response,
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
if ((0, errors_1.isAuthError)(error)) {
|
||||
return { data: null, error };
|
||||
}
|
||||
return {
|
||||
data: null,
|
||||
error: new errors_1.AuthUnknownError('Unexpected error in authenticate', error),
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Complete WebAuthn registration flow.
|
||||
* Performs enrollment, challenge, and verification in a single operation for new credentials.
|
||||
*
|
||||
* @experimental This method is experimental and may change in future releases
|
||||
* @param {Object} params - Registration parameters
|
||||
* @param {string} params.friendlyName - User-friendly name for the credential
|
||||
* @param {string} params.rpId - Relying Party ID (defaults to current hostname)
|
||||
* @param {string[]} params.rpOrigins - Allowed origins (defaults to current origin)
|
||||
* @param {AbortSignal} params.signal - Optional abort signal
|
||||
* @param {PublicKeyCredentialCreationOptionsFuture} overrides - Override options for navigator.credentials.create
|
||||
* @returns {Promise<RequestResult<AuthMFAVerifyResponseData, WebAuthnError | AuthError>>} Registration result
|
||||
* @see {@link https://w3c.github.io/webauthn/#sctn-registering-a-new-credential W3C WebAuthn Spec - Registration Ceremony}
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredentialCreationOptions MDN - PublicKeyCredentialCreationOptions}
|
||||
*/
|
||||
async _register({ friendlyName, webauthn: { rpId = typeof window !== 'undefined' ? window.location.hostname : undefined, rpOrigins = typeof window !== 'undefined' ? [window.location.origin] : undefined, signal, } = {}, }, overrides) {
|
||||
if (!rpId) {
|
||||
return {
|
||||
data: null,
|
||||
error: new errors_1.AuthError('rpId is required for WebAuthn registration'),
|
||||
};
|
||||
}
|
||||
try {
|
||||
if (!browserSupportsWebAuthn()) {
|
||||
return {
|
||||
data: null,
|
||||
error: new errors_1.AuthUnknownError('Browser does not support WebAuthn', null),
|
||||
};
|
||||
}
|
||||
// Enroll factor
|
||||
const { data: factor, error: enrollError } = await this._enroll({
|
||||
friendlyName,
|
||||
});
|
||||
if (!factor) {
|
||||
await this.client.mfa
|
||||
.listFactors()
|
||||
.then((factors) => {
|
||||
var _a;
|
||||
return (_a = factors.data) === null || _a === void 0 ? void 0 : _a.all.find((v) => v.factor_type === 'webauthn' &&
|
||||
v.friendly_name === friendlyName &&
|
||||
v.status !== 'unverified');
|
||||
})
|
||||
.then((factor) => (factor ? this.client.mfa.unenroll({ factorId: factor === null || factor === void 0 ? void 0 : factor.id }) : void 0));
|
||||
return { data: null, error: enrollError };
|
||||
}
|
||||
// Get challenge and create credential
|
||||
const { data: challengeResponse, error: challengeError } = await this._challenge({
|
||||
factorId: factor.id,
|
||||
friendlyName: factor.friendly_name,
|
||||
webauthn: { rpId, rpOrigins },
|
||||
signal,
|
||||
}, {
|
||||
create: overrides,
|
||||
});
|
||||
if (!challengeResponse) {
|
||||
return { data: null, error: challengeError };
|
||||
}
|
||||
return this._verify({
|
||||
factorId: factor.id,
|
||||
challengeId: challengeResponse.challengeId,
|
||||
webauthn: {
|
||||
rpId,
|
||||
rpOrigins,
|
||||
type: challengeResponse.webauthn.type,
|
||||
credential_response: challengeResponse.webauthn.credential_response,
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
if ((0, errors_1.isAuthError)(error)) {
|
||||
return { data: null, error };
|
||||
}
|
||||
return {
|
||||
data: null,
|
||||
error: new errors_1.AuthUnknownError('Unexpected error in register', error),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.WebAuthnApi = WebAuthnApi;
|
||||
//# sourceMappingURL=webauthn.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../../.nitro/tslib@2.8.1
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "@supabase/auth-js",
|
||||
"version": "2.93.3",
|
||||
"private": false,
|
||||
"description": "Official SDK for Supabase Auth",
|
||||
"keywords": [
|
||||
"auth",
|
||||
"supabase",
|
||||
"auth",
|
||||
"authentication"
|
||||
],
|
||||
"homepage": "https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js",
|
||||
"bugs": "https://github.com/supabase/supabase-js/issues",
|
||||
"license": "MIT",
|
||||
"author": "Supabase",
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"main": "dist/main/index.js",
|
||||
"module": "dist/module/index.js",
|
||||
"types": "dist/module/index.d.ts",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/supabase/supabase-js.git",
|
||||
"directory": "packages/core/auth-js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm run build:main && npm run build:module",
|
||||
"build:main": "tsc -p tsconfig.json",
|
||||
"build:module": "tsc -p tsconfig.module.json",
|
||||
"docs": "typedoc src/index.ts --out docs/v2 --excludePrivate --excludeProtected",
|
||||
"docs:json": "typedoc --json docs/v2/spec.json --excludeExternals --excludePrivate --excludeProtected src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^2.8.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
}
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.FunctionsClient = void 0;
|
||||
const tslib_1 = require("tslib");
|
||||
const helper_1 = require("./helper");
|
||||
const types_1 = require("./types");
|
||||
/**
|
||||
* Client for invoking Supabase Edge Functions.
|
||||
*/
|
||||
class FunctionsClient {
|
||||
/**
|
||||
* Creates a new Functions client bound to an Edge Functions URL.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { FunctionsClient, FunctionRegion } from '@supabase/functions-js'
|
||||
*
|
||||
* const functions = new FunctionsClient('https://xyzcompany.supabase.co/functions/v1', {
|
||||
* headers: { apikey: 'public-anon-key' },
|
||||
* region: FunctionRegion.UsEast1,
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
constructor(url, { headers = {}, customFetch, region = types_1.FunctionRegion.Any, } = {}) {
|
||||
this.url = url;
|
||||
this.headers = headers;
|
||||
this.region = region;
|
||||
this.fetch = (0, helper_1.resolveFetch)(customFetch);
|
||||
}
|
||||
/**
|
||||
* Updates the authorization header
|
||||
* @param token - the new jwt token sent in the authorisation header
|
||||
* @example
|
||||
* ```ts
|
||||
* functions.setAuth(session.access_token)
|
||||
* ```
|
||||
*/
|
||||
setAuth(token) {
|
||||
this.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
/**
|
||||
* Invokes a function
|
||||
* @param functionName - The name of the Function to invoke.
|
||||
* @param options - Options for invoking the Function.
|
||||
* @example
|
||||
* ```ts
|
||||
* const { data, error } = await functions.invoke('hello-world', {
|
||||
* body: { name: 'Ada' },
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
invoke(functionName_1) {
|
||||
return tslib_1.__awaiter(this, arguments, void 0, function* (functionName, options = {}) {
|
||||
var _a;
|
||||
let timeoutId;
|
||||
let timeoutController;
|
||||
try {
|
||||
const { headers, method, body: functionArgs, signal, timeout } = options;
|
||||
let _headers = {};
|
||||
let { region } = options;
|
||||
if (!region) {
|
||||
region = this.region;
|
||||
}
|
||||
// Add region as query parameter using URL API
|
||||
const url = new URL(`${this.url}/${functionName}`);
|
||||
if (region && region !== 'any') {
|
||||
_headers['x-region'] = region;
|
||||
url.searchParams.set('forceFunctionRegion', region);
|
||||
}
|
||||
let body;
|
||||
if (functionArgs &&
|
||||
((headers && !Object.prototype.hasOwnProperty.call(headers, 'Content-Type')) || !headers)) {
|
||||
if ((typeof Blob !== 'undefined' && functionArgs instanceof Blob) ||
|
||||
functionArgs instanceof ArrayBuffer) {
|
||||
// will work for File as File inherits Blob
|
||||
// also works for ArrayBuffer as it is the same underlying structure as a Blob
|
||||
_headers['Content-Type'] = 'application/octet-stream';
|
||||
body = functionArgs;
|
||||
}
|
||||
else if (typeof functionArgs === 'string') {
|
||||
// plain string
|
||||
_headers['Content-Type'] = 'text/plain';
|
||||
body = functionArgs;
|
||||
}
|
||||
else if (typeof FormData !== 'undefined' && functionArgs instanceof FormData) {
|
||||
// don't set content-type headers
|
||||
// Request will automatically add the right boundary value
|
||||
body = functionArgs;
|
||||
}
|
||||
else {
|
||||
// default, assume this is JSON
|
||||
_headers['Content-Type'] = 'application/json';
|
||||
body = JSON.stringify(functionArgs);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (functionArgs &&
|
||||
typeof functionArgs !== 'string' &&
|
||||
!(typeof Blob !== 'undefined' && functionArgs instanceof Blob) &&
|
||||
!(functionArgs instanceof ArrayBuffer) &&
|
||||
!(typeof FormData !== 'undefined' && functionArgs instanceof FormData)) {
|
||||
body = JSON.stringify(functionArgs);
|
||||
}
|
||||
else {
|
||||
body = functionArgs;
|
||||
}
|
||||
}
|
||||
// Handle timeout by creating an AbortController
|
||||
let effectiveSignal = signal;
|
||||
if (timeout) {
|
||||
timeoutController = new AbortController();
|
||||
timeoutId = setTimeout(() => timeoutController.abort(), timeout);
|
||||
// If user provided their own signal, we need to respect both
|
||||
if (signal) {
|
||||
effectiveSignal = timeoutController.signal;
|
||||
// If the user's signal is aborted, abort our timeout controller too
|
||||
signal.addEventListener('abort', () => timeoutController.abort());
|
||||
}
|
||||
else {
|
||||
effectiveSignal = timeoutController.signal;
|
||||
}
|
||||
}
|
||||
const response = yield this.fetch(url.toString(), {
|
||||
method: method || 'POST',
|
||||
// headers priority is (high to low):
|
||||
// 1. invoke-level headers
|
||||
// 2. client-level headers
|
||||
// 3. default Content-Type header
|
||||
headers: Object.assign(Object.assign(Object.assign({}, _headers), this.headers), headers),
|
||||
body,
|
||||
signal: effectiveSignal,
|
||||
}).catch((fetchError) => {
|
||||
throw new types_1.FunctionsFetchError(fetchError);
|
||||
});
|
||||
const isRelayError = response.headers.get('x-relay-error');
|
||||
if (isRelayError && isRelayError === 'true') {
|
||||
throw new types_1.FunctionsRelayError(response);
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new types_1.FunctionsHttpError(response);
|
||||
}
|
||||
let responseType = ((_a = response.headers.get('Content-Type')) !== null && _a !== void 0 ? _a : 'text/plain').split(';')[0].trim();
|
||||
let data;
|
||||
if (responseType === 'application/json') {
|
||||
data = yield response.json();
|
||||
}
|
||||
else if (responseType === 'application/octet-stream' ||
|
||||
responseType === 'application/pdf') {
|
||||
data = yield response.blob();
|
||||
}
|
||||
else if (responseType === 'text/event-stream') {
|
||||
data = response;
|
||||
}
|
||||
else if (responseType === 'multipart/form-data') {
|
||||
data = yield response.formData();
|
||||
}
|
||||
else {
|
||||
// default to text
|
||||
data = yield response.text();
|
||||
}
|
||||
return { data, error: null, response };
|
||||
}
|
||||
catch (error) {
|
||||
return {
|
||||
data: null,
|
||||
error,
|
||||
response: error instanceof types_1.FunctionsHttpError || error instanceof types_1.FunctionsRelayError
|
||||
? error.context
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
finally {
|
||||
// Clear the timeout if it was set
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.FunctionsClient = FunctionsClient;
|
||||
//# sourceMappingURL=FunctionsClient.js.map
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.resolveFetch = void 0;
|
||||
const resolveFetch = (customFetch) => {
|
||||
if (customFetch) {
|
||||
return (...args) => customFetch(...args);
|
||||
}
|
||||
return (...args) => fetch(...args);
|
||||
};
|
||||
exports.resolveFetch = resolveFetch;
|
||||
//# sourceMappingURL=helper.js.map
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.FunctionRegion = exports.FunctionsRelayError = exports.FunctionsHttpError = exports.FunctionsFetchError = exports.FunctionsError = exports.FunctionsClient = void 0;
|
||||
var FunctionsClient_1 = require("./FunctionsClient");
|
||||
Object.defineProperty(exports, "FunctionsClient", { enumerable: true, get: function () { return FunctionsClient_1.FunctionsClient; } });
|
||||
var types_1 = require("./types");
|
||||
Object.defineProperty(exports, "FunctionsError", { enumerable: true, get: function () { return types_1.FunctionsError; } });
|
||||
Object.defineProperty(exports, "FunctionsFetchError", { enumerable: true, get: function () { return types_1.FunctionsFetchError; } });
|
||||
Object.defineProperty(exports, "FunctionsHttpError", { enumerable: true, get: function () { return types_1.FunctionsHttpError; } });
|
||||
Object.defineProperty(exports, "FunctionsRelayError", { enumerable: true, get: function () { return types_1.FunctionsRelayError; } });
|
||||
Object.defineProperty(exports, "FunctionRegion", { enumerable: true, get: function () { return types_1.FunctionRegion; } });
|
||||
//# sourceMappingURL=index.js.map
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.FunctionRegion = exports.FunctionsHttpError = exports.FunctionsRelayError = exports.FunctionsFetchError = exports.FunctionsError = void 0;
|
||||
/**
|
||||
* Base error for Supabase Edge Function invocations.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { FunctionsError } from '@supabase/functions-js'
|
||||
*
|
||||
* throw new FunctionsError('Unexpected error invoking function', 'FunctionsError', {
|
||||
* requestId: 'abc123',
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
class FunctionsError extends Error {
|
||||
constructor(message, name = 'FunctionsError', context) {
|
||||
super(message);
|
||||
this.name = name;
|
||||
this.context = context;
|
||||
}
|
||||
}
|
||||
exports.FunctionsError = FunctionsError;
|
||||
/**
|
||||
* Error thrown when the network request to an Edge Function fails.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { FunctionsFetchError } from '@supabase/functions-js'
|
||||
*
|
||||
* throw new FunctionsFetchError({ requestId: 'abc123' })
|
||||
* ```
|
||||
*/
|
||||
class FunctionsFetchError extends FunctionsError {
|
||||
constructor(context) {
|
||||
super('Failed to send a request to the Edge Function', 'FunctionsFetchError', context);
|
||||
}
|
||||
}
|
||||
exports.FunctionsFetchError = FunctionsFetchError;
|
||||
/**
|
||||
* Error thrown when the Supabase relay cannot reach the Edge Function.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { FunctionsRelayError } from '@supabase/functions-js'
|
||||
*
|
||||
* throw new FunctionsRelayError({ region: 'us-east-1' })
|
||||
* ```
|
||||
*/
|
||||
class FunctionsRelayError extends FunctionsError {
|
||||
constructor(context) {
|
||||
super('Relay Error invoking the Edge Function', 'FunctionsRelayError', context);
|
||||
}
|
||||
}
|
||||
exports.FunctionsRelayError = FunctionsRelayError;
|
||||
/**
|
||||
* Error thrown when the Edge Function returns a non-2xx status code.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { FunctionsHttpError } from '@supabase/functions-js'
|
||||
*
|
||||
* throw new FunctionsHttpError({ status: 500 })
|
||||
* ```
|
||||
*/
|
||||
class FunctionsHttpError extends FunctionsError {
|
||||
constructor(context) {
|
||||
super('Edge Function returned a non-2xx status code', 'FunctionsHttpError', context);
|
||||
}
|
||||
}
|
||||
exports.FunctionsHttpError = FunctionsHttpError;
|
||||
// Define the enum for the 'region' property
|
||||
var FunctionRegion;
|
||||
(function (FunctionRegion) {
|
||||
FunctionRegion["Any"] = "any";
|
||||
FunctionRegion["ApNortheast1"] = "ap-northeast-1";
|
||||
FunctionRegion["ApNortheast2"] = "ap-northeast-2";
|
||||
FunctionRegion["ApSouth1"] = "ap-south-1";
|
||||
FunctionRegion["ApSoutheast1"] = "ap-southeast-1";
|
||||
FunctionRegion["ApSoutheast2"] = "ap-southeast-2";
|
||||
FunctionRegion["CaCentral1"] = "ca-central-1";
|
||||
FunctionRegion["EuCentral1"] = "eu-central-1";
|
||||
FunctionRegion["EuWest1"] = "eu-west-1";
|
||||
FunctionRegion["EuWest2"] = "eu-west-2";
|
||||
FunctionRegion["EuWest3"] = "eu-west-3";
|
||||
FunctionRegion["SaEast1"] = "sa-east-1";
|
||||
FunctionRegion["UsEast1"] = "us-east-1";
|
||||
FunctionRegion["UsWest1"] = "us-west-1";
|
||||
FunctionRegion["UsWest2"] = "us-west-2";
|
||||
})(FunctionRegion || (exports.FunctionRegion = FunctionRegion = {}));
|
||||
//# sourceMappingURL=types.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../../.nitro/tslib@2.8.1
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "@supabase/functions-js",
|
||||
"version": "2.93.3",
|
||||
"description": "JS SDK to interact with Supabase Functions.",
|
||||
"main": "dist/main/index.js",
|
||||
"module": "dist/module/index.js",
|
||||
"types": "dist/module/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"build": "npm run build:main && npm run build:module",
|
||||
"build:main": "tsc -p tsconfig.json",
|
||||
"build:module": "tsc -p tsconfig.module.json",
|
||||
"docs": "typedoc src/index.ts --out docs/v2",
|
||||
"docs:json": "typedoc --json docs/v2/spec.json --excludeExternals src/index.ts",
|
||||
"test": "jest",
|
||||
"test:ci": "jest --coverage"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/supabase/supabase-js.git",
|
||||
"directory": "packages/core/functions-js"
|
||||
},
|
||||
"keywords": [
|
||||
"functions",
|
||||
"supabase"
|
||||
],
|
||||
"author": "Supabase",
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/supabase/supabase-js/issues"
|
||||
},
|
||||
"homepage": "https://github.com/supabase/supabase-js/tree/master/packages/core/functions-js",
|
||||
"dependencies": {
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bs-logger": "^0.2.6",
|
||||
"nanoid": "^3.3.1",
|
||||
"openai": "^4.52.5",
|
||||
"testcontainers": "^8.5.1"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
}
|
||||
+1253
File diff suppressed because it is too large
Load Diff
+69
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"name": "@supabase/postgrest-js",
|
||||
"version": "2.93.3",
|
||||
"description": "Isomorphic PostgREST client",
|
||||
"keywords": [
|
||||
"postgrest",
|
||||
"supabase"
|
||||
],
|
||||
"homepage": "https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js",
|
||||
"bugs": "https://github.com/supabase/supabase-js/issues",
|
||||
"license": "MIT",
|
||||
"author": "Supabase",
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "dist/index.d.cts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/index.d.mts",
|
||||
"default": "./dist/index.mjs"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/index.d.cts",
|
||||
"default": "./dist/index.cjs"
|
||||
}
|
||||
},
|
||||
"./dist/*": "./dist/*",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/supabase/supabase-js.git",
|
||||
"directory": "packages/core/postgrest-js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsdown",
|
||||
"build:watch": "tsdown --watch",
|
||||
"format": "node scripts/format.js",
|
||||
"format:check": "node scripts/format.js check",
|
||||
"docs": "typedoc src/index.ts --out docs/v2",
|
||||
"docs:json": "typedoc --json docs/v2/spec.json --excludeExternals src/index.ts",
|
||||
"test:run": "jest --runInBand --coverage -u",
|
||||
"test:smoke": "node test/smoke.cjs && node test/smoke.mjs",
|
||||
"test:types": "tstyche",
|
||||
"test:types:ci": "tstyche --target '4.7,5.5,latest'",
|
||||
"test:types:watch": "chokidar 'src/**/*.ts' 'test/**/*.ts' -c 'npm run test:types'",
|
||||
"type-check": "tsc --noEmit --project tsconfig.json",
|
||||
"type-check:test": "tsc --noEmit --project tsconfig.test.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chokidar-cli": "^3.0.0",
|
||||
"node-abort-controller": "^3.0.1",
|
||||
"tstyche": "^4.3.0",
|
||||
"type-fest": "^4.32.0",
|
||||
"wait-for-localhost-cli": "^4.0.0",
|
||||
"zod": "^3.25.76",
|
||||
"prettier": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
}
|
||||
+644
@@ -0,0 +1,644 @@
|
||||
"use strict";
|
||||
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 = 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) {
|
||||
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT["ALL"] = "*";
|
||||
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT["INSERT"] = "INSERT";
|
||||
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT["UPDATE"] = "UPDATE";
|
||||
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT["DELETE"] = "DELETE";
|
||||
})(REALTIME_POSTGRES_CHANGES_LISTEN_EVENT || (exports.REALTIME_POSTGRES_CHANGES_LISTEN_EVENT = REALTIME_POSTGRES_CHANGES_LISTEN_EVENT = {}));
|
||||
var REALTIME_LISTEN_TYPES;
|
||||
(function (REALTIME_LISTEN_TYPES) {
|
||||
REALTIME_LISTEN_TYPES["BROADCAST"] = "broadcast";
|
||||
REALTIME_LISTEN_TYPES["PRESENCE"] = "presence";
|
||||
REALTIME_LISTEN_TYPES["POSTGRES_CHANGES"] = "postgres_changes";
|
||||
REALTIME_LISTEN_TYPES["SYSTEM"] = "system";
|
||||
})(REALTIME_LISTEN_TYPES || (exports.REALTIME_LISTEN_TYPES = REALTIME_LISTEN_TYPES = {}));
|
||||
var REALTIME_SUBSCRIBE_STATES;
|
||||
(function (REALTIME_SUBSCRIBE_STATES) {
|
||||
REALTIME_SUBSCRIBE_STATES["SUBSCRIBED"] = "SUBSCRIBED";
|
||||
REALTIME_SUBSCRIBE_STATES["TIMED_OUT"] = "TIMED_OUT";
|
||||
REALTIME_SUBSCRIBE_STATES["CLOSED"] = "CLOSED";
|
||||
REALTIME_SUBSCRIBE_STATES["CHANNEL_ERROR"] = "CHANNEL_ERROR";
|
||||
})(REALTIME_SUBSCRIBE_STATES || (exports.REALTIME_SUBSCRIBE_STATES = REALTIME_SUBSCRIBE_STATES = {}));
|
||||
exports.REALTIME_CHANNEL_STATES = constants_1.CHANNEL_STATES;
|
||||
/** A channel is the basic building block of Realtime
|
||||
* and narrows the scope of data flow to subscribed clients.
|
||||
* You can think of a channel as a chatroom where participants are able to see who's online
|
||||
* and send and receive messages.
|
||||
*/
|
||||
class RealtimeChannel {
|
||||
/**
|
||||
* 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;
|
||||
this.bindings = {};
|
||||
this.state = constants_1.CHANNEL_STATES.closed;
|
||||
this.joinedOnce = false;
|
||||
this.pushBuffer = [];
|
||||
this.subTopic = topic.replace(/^realtime:/i, '');
|
||||
this.params.config = Object.assign({
|
||||
broadcast: { ack: false, self: false },
|
||||
presence: { key: '', enabled: false },
|
||||
private: false,
|
||||
}, params.config);
|
||||
this.timeout = this.socket.timeout;
|
||||
this.joinPush = new push_1.default(this, constants_1.CHANNEL_EVENTS.join, this.params, this.timeout);
|
||||
this.rejoinTimer = new timer_1.default(() => this._rejoinUntilConnected(), this.socket.reconnectAfterMs);
|
||||
this.joinPush.receive('ok', () => {
|
||||
this.state = constants_1.CHANNEL_STATES.joined;
|
||||
this.rejoinTimer.reset();
|
||||
this.pushBuffer.forEach((pushEvent) => pushEvent.send());
|
||||
this.pushBuffer = [];
|
||||
});
|
||||
this._onClose(() => {
|
||||
this.rejoinTimer.reset();
|
||||
this.socket.log('channel', `close ${this.topic} ${this._joinRef()}`);
|
||||
this.state = constants_1.CHANNEL_STATES.closed;
|
||||
this.socket._remove(this);
|
||||
});
|
||||
this._onError((reason) => {
|
||||
if (this._isLeaving() || this._isClosed()) {
|
||||
return;
|
||||
}
|
||||
this.socket.log('channel', `error ${this.topic}`, reason);
|
||||
this.state = constants_1.CHANNEL_STATES.errored;
|
||||
this.rejoinTimer.scheduleTimeout();
|
||||
});
|
||||
this.joinPush.receive('timeout', () => {
|
||||
if (!this._isJoining()) {
|
||||
return;
|
||||
}
|
||||
this.socket.log('channel', `timeout ${this.topic}`, this.joinPush.timeout);
|
||||
this.state = constants_1.CHANNEL_STATES.errored;
|
||||
this.rejoinTimer.scheduleTimeout();
|
||||
});
|
||||
this.joinPush.receive('error', (reason) => {
|
||||
if (this._isLeaving() || this._isClosed()) {
|
||||
return;
|
||||
}
|
||||
this.socket.log('channel', `error ${this.topic}`, reason);
|
||||
this.state = constants_1.CHANNEL_STATES.errored;
|
||||
this.rejoinTimer.scheduleTimeout();
|
||||
});
|
||||
this._on(constants_1.CHANNEL_EVENTS.reply, {}, (payload, ref) => {
|
||||
this._trigger(this._replyEventName(ref), payload);
|
||||
});
|
||||
this.presence = new RealtimePresence_1.default(this);
|
||||
this.broadcastEndpointURL = (0, transformers_1.httpEndpointURL)(this.socket.endPoint);
|
||||
this.private = this.params.config.private || false;
|
||||
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, _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) ||
|
||||
((_c = this.params.config.presence) === null || _c === void 0 ? void 0 : _c.enabled) === true;
|
||||
const accessTokenPayload = {};
|
||||
const config = {
|
||||
broadcast,
|
||||
presence: Object.assign(Object.assign({}, presence), { enabled: presence_enabled }),
|
||||
postgres_changes,
|
||||
private: isPrivate,
|
||||
};
|
||||
if (this.socket.accessTokenValue) {
|
||||
accessTokenPayload.access_token = this.socket.accessTokenValue;
|
||||
}
|
||||
this._onError((e) => callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR, e));
|
||||
this._onClose(() => callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.CLOSED));
|
||||
this.updateJoinPayload(Object.assign({ config }, accessTokenPayload));
|
||||
this.joinedOnce = true;
|
||||
this._rejoin(timeout);
|
||||
this.joinPush
|
||||
.receive('ok', async ({ postgres_changes }) => {
|
||||
var _a;
|
||||
// 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;
|
||||
}
|
||||
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 &&
|
||||
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 {
|
||||
this.unsubscribe();
|
||||
this.state = constants_1.CHANNEL_STATES.errored;
|
||||
callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR, new Error('mismatch between server and client bindings for postgres changes'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.bindings.postgres_changes = newPostgresBindings;
|
||||
callback && callback(REALTIME_SUBSCRIBE_STATES.SUBSCRIBED);
|
||||
return;
|
||||
}
|
||||
})
|
||||
.receive('error', (error) => {
|
||||
this.state = constants_1.CHANNEL_STATES.errored;
|
||||
callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR, new Error(JSON.stringify(Object.values(error).join(', ') || 'error')));
|
||||
return;
|
||||
})
|
||||
.receive('timeout', () => {
|
||||
callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.TIMED_OUT);
|
||||
return;
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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',
|
||||
event: 'track',
|
||||
payload,
|
||||
}, opts.timeout || this.timeout);
|
||||
}
|
||||
/**
|
||||
* Removes the current presence state for this client.
|
||||
*/
|
||||
async untrack(opts = {}) {
|
||||
return await this.send({
|
||||
type: 'presence',
|
||||
event: 'untrack',
|
||||
}, opts);
|
||||
}
|
||||
on(type, filter, callback) {
|
||||
if (this.state === constants_1.CHANNEL_STATES.joined && type === REALTIME_LISTEN_TYPES.PRESENCE) {
|
||||
this.socket.log('channel', `resubscribe to ${this.topic} due to change in presence callbacks on joined channel`);
|
||||
this.unsubscribe().then(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.
|
||||
*
|
||||
* @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') {
|
||||
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 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: 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'));
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
/**
|
||||
* Leaves the channel.
|
||||
*
|
||||
* Unsubscribes from server events, and instructs channel to terminate on server.
|
||||
* Triggers onClose() hooks.
|
||||
*
|
||||
* To receive leave acknowledgements, use the a `receive` hook to bind to the server ack, ie:
|
||||
* channel.unsubscribe().receive("ok", () => alert("left!") )
|
||||
*/
|
||||
unsubscribe(timeout = this.timeout) {
|
||||
this.state = constants_1.CHANNEL_STATES.leaving;
|
||||
const onClose = () => {
|
||||
this.socket.log('channel', `leave ${this.topic}`);
|
||||
this._trigger(constants_1.CHANNEL_EVENTS.close, 'leave', this._joinRef());
|
||||
};
|
||||
this.joinPush.destroy();
|
||||
let leavePush = null;
|
||||
return new Promise((resolve) => {
|
||||
leavePush = new push_1.default(this, constants_1.CHANNEL_EVENTS.leave, {}, timeout);
|
||||
leavePush
|
||||
.receive('ok', () => {
|
||||
onClose();
|
||||
resolve('ok');
|
||||
})
|
||||
.receive('timeout', () => {
|
||||
onClose();
|
||||
resolve('timed out');
|
||||
})
|
||||
.receive('error', () => {
|
||||
resolve('error');
|
||||
});
|
||||
leavePush.send();
|
||||
if (!this._canPush()) {
|
||||
leavePush.trigger('ok', {});
|
||||
}
|
||||
}).finally(() => {
|
||||
leavePush === null || leavePush === void 0 ? void 0 : leavePush.destroy();
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Teardown the channel.
|
||||
*
|
||||
* Destroys and stops related timers.
|
||||
*/
|
||||
teardown() {
|
||||
this.pushBuffer.forEach((push) => push.destroy());
|
||||
this.pushBuffer = [];
|
||||
this.rejoinTimer.reset();
|
||||
this.joinPush.destroy();
|
||||
this.state = constants_1.CHANNEL_STATES.closed;
|
||||
this.bindings = {};
|
||||
}
|
||||
/** @internal */
|
||||
async _fetchWithTimeout(url, options, timeout) {
|
||||
const controller = new AbortController();
|
||||
const id = setTimeout(() => controller.abort(), timeout);
|
||||
const response = await this.socket.fetch(url, Object.assign(Object.assign({}, options), { signal: controller.signal }));
|
||||
clearTimeout(id);
|
||||
return response;
|
||||
}
|
||||
/** @internal */
|
||||
_push(event, payload, timeout = this.timeout) {
|
||||
if (!this.joinedOnce) {
|
||||
throw `tried to push '${event}' to '${this.topic}' before joining. Use channel.subscribe() before pushing events`;
|
||||
}
|
||||
let pushEvent = new push_1.default(this, event, payload, timeout);
|
||||
if (this._canPush()) {
|
||||
pushEvent.send();
|
||||
}
|
||||
else {
|
||||
this._addToPushBuffer(pushEvent);
|
||||
}
|
||||
return pushEvent;
|
||||
}
|
||||
/** @internal */
|
||||
_addToPushBuffer(pushEvent) {
|
||||
pushEvent.startTimeout();
|
||||
this.pushBuffer.push(pushEvent);
|
||||
// Enforce buffer size limit
|
||||
if (this.pushBuffer.length > constants_1.MAX_PUSH_BUFFER_SIZE) {
|
||||
const removedPush = this.pushBuffer.shift();
|
||||
if (removedPush) {
|
||||
removedPush.destroy();
|
||||
this.socket.log('channel', `discarded push due to buffer overflow: ${removedPush.event}`, removedPush.payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Overridable message hook
|
||||
*
|
||||
* Receives all events for specialized message handling before dispatching to the channel callbacks.
|
||||
* Must return the payload, modified or unmodified.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_onMessage(_event, payload, _ref) {
|
||||
return payload;
|
||||
}
|
||||
/** @internal */
|
||||
_isMember(topic) {
|
||||
return this.topic === topic;
|
||||
}
|
||||
/** @internal */
|
||||
_joinRef() {
|
||||
return this.joinPush.ref;
|
||||
}
|
||||
/** @internal */
|
||||
_trigger(type, payload, ref) {
|
||||
var _a, _b;
|
||||
const typeLower = type.toLocaleLowerCase();
|
||||
const { close, error, leave, join } = constants_1.CHANNEL_EVENTS;
|
||||
const events = [close, error, leave, join];
|
||||
if (ref && events.indexOf(typeLower) >= 0 && ref !== this._joinRef()) {
|
||||
return;
|
||||
}
|
||||
let handledPayload = this._onMessage(typeLower, payload, ref);
|
||||
if (payload && !handledPayload) {
|
||||
throw 'channel onMessage callbacks must return the payload, modified or unmodified';
|
||||
}
|
||||
if (['insert', 'update', 'delete'].includes(typeLower)) {
|
||||
(_a = this.bindings.postgres_changes) === null || _a === void 0 ? void 0 : _a.filter((bind) => {
|
||||
var _a, _b, _c;
|
||||
return ((_a = bind.filter) === null || _a === void 0 ? void 0 : _a.event) === '*' || ((_c = (_b = bind.filter) === null || _b === void 0 ? void 0 : _b.event) === null || _c === void 0 ? void 0 : _c.toLocaleLowerCase()) === typeLower;
|
||||
}).map((bind) => bind.callback(handledPayload, ref));
|
||||
}
|
||||
else {
|
||||
(_b = this.bindings[typeLower]) === null || _b === void 0 ? void 0 : _b.filter((bind) => {
|
||||
var _a, _b, _c, _d, _e, _f;
|
||||
if (['broadcast', 'presence', 'postgres_changes'].includes(typeLower)) {
|
||||
if ('id' in bind) {
|
||||
const bindId = bind.id;
|
||||
const bindEvent = (_a = bind.filter) === null || _a === void 0 ? void 0 : _a.event;
|
||||
return (bindId &&
|
||||
((_b = payload.ids) === null || _b === void 0 ? void 0 : _b.includes(bindId)) &&
|
||||
(bindEvent === '*' ||
|
||||
(bindEvent === null || bindEvent === void 0 ? void 0 : bindEvent.toLocaleLowerCase()) === ((_c = payload.data) === null || _c === void 0 ? void 0 : _c.type.toLocaleLowerCase())));
|
||||
}
|
||||
else {
|
||||
const bindEvent = (_e = (_d = bind === null || bind === void 0 ? void 0 : bind.filter) === null || _d === void 0 ? void 0 : _d.event) === null || _e === void 0 ? void 0 : _e.toLocaleLowerCase();
|
||||
return bindEvent === '*' || bindEvent === ((_f = payload === null || payload === void 0 ? void 0 : payload.event) === null || _f === void 0 ? void 0 : _f.toLocaleLowerCase());
|
||||
}
|
||||
}
|
||||
else {
|
||||
return bind.type.toLocaleLowerCase() === typeLower;
|
||||
}
|
||||
}).map((bind) => {
|
||||
if (typeof handledPayload === 'object' && 'ids' in handledPayload) {
|
||||
const postgresChanges = handledPayload.data;
|
||||
const { schema, table, commit_timestamp, type, errors } = postgresChanges;
|
||||
const enrichedPayload = {
|
||||
schema: schema,
|
||||
table: table,
|
||||
commit_timestamp: commit_timestamp,
|
||||
eventType: type,
|
||||
new: {},
|
||||
old: {},
|
||||
errors: errors,
|
||||
};
|
||||
handledPayload = Object.assign(Object.assign({}, enrichedPayload), this._getPayloadRecords(postgresChanges));
|
||||
}
|
||||
bind.callback(handledPayload, ref);
|
||||
});
|
||||
}
|
||||
}
|
||||
/** @internal */
|
||||
_isClosed() {
|
||||
return this.state === constants_1.CHANNEL_STATES.closed;
|
||||
}
|
||||
/** @internal */
|
||||
_isJoined() {
|
||||
return this.state === constants_1.CHANNEL_STATES.joined;
|
||||
}
|
||||
/** @internal */
|
||||
_isJoining() {
|
||||
return this.state === constants_1.CHANNEL_STATES.joining;
|
||||
}
|
||||
/** @internal */
|
||||
_isLeaving() {
|
||||
return this.state === constants_1.CHANNEL_STATES.leaving;
|
||||
}
|
||||
/** @internal */
|
||||
_replyEventName(ref) {
|
||||
return `chan_reply_${ref}`;
|
||||
}
|
||||
/** @internal */
|
||||
_on(type, filter, callback) {
|
||||
const typeLower = type.toLocaleLowerCase();
|
||||
const binding = {
|
||||
type: typeLower,
|
||||
filter: filter,
|
||||
callback: callback,
|
||||
};
|
||||
if (this.bindings[typeLower]) {
|
||||
this.bindings[typeLower].push(binding);
|
||||
}
|
||||
else {
|
||||
this.bindings[typeLower] = [binding];
|
||||
}
|
||||
return this;
|
||||
}
|
||||
/** @internal */
|
||||
_off(type, filter) {
|
||||
const typeLower = type.toLocaleLowerCase();
|
||||
if (this.bindings[typeLower]) {
|
||||
this.bindings[typeLower] = this.bindings[typeLower].filter((bind) => {
|
||||
var _a;
|
||||
return !(((_a = bind.type) === null || _a === void 0 ? void 0 : _a.toLocaleLowerCase()) === typeLower &&
|
||||
RealtimeChannel.isEqual(bind.filter, filter));
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
/** @internal */
|
||||
static isEqual(obj1, obj2) {
|
||||
if (Object.keys(obj1).length !== Object.keys(obj2).length) {
|
||||
return false;
|
||||
}
|
||||
for (const k in obj1) {
|
||||
if (obj1[k] !== obj2[k]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* 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();
|
||||
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
|
||||
+842
@@ -0,0 +1,842 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
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 = tslib_1.__importDefault(require("./lib/serializer"));
|
||||
const timer_1 = tslib_1.__importDefault(require("./lib/timer"));
|
||||
const transformers_1 = require("./lib/transformers");
|
||||
const RealtimeChannel_1 = tslib_1.__importDefault(require("./RealtimeChannel"));
|
||||
const noop = () => { };
|
||||
// Connection-related constants
|
||||
const CONNECTION_TIMEOUTS = {
|
||||
HEARTBEAT_INTERVAL: 25000,
|
||||
RECONNECT_DELAY: 10,
|
||||
HEARTBEAT_TIMEOUT_FALLBACK: 100,
|
||||
};
|
||||
const RECONNECT_INTERVALS = [1000, 2000, 5000, 10000];
|
||||
const DEFAULT_RECONNECT_FALLBACK = 10000;
|
||||
const WORKER_SCRIPT = `
|
||||
addEventListener("message", (e) => {
|
||||
if (e.data.event === "start") {
|
||||
setInterval(() => postMessage({ event: "keepAlive" }), e.data.interval);
|
||||
}
|
||||
});`;
|
||||
class RealtimeClient {
|
||||
/**
|
||||
* Initializes the Socket.
|
||||
*
|
||||
* @param endPoint The string WebSocket endpoint, ie, "ws://example.com/socket", "wss://example.com", "/socket" (inherited host & protocol)
|
||||
* @param httpEndpoint The string HTTP endpoint, ie, "https://example.com", "/" (inherited host & protocol)
|
||||
* @param options.transport The Websocket Transport, for example WebSocket. This can be a custom implementation
|
||||
* @param options.timeout The default timeout in milliseconds to trigger push timeouts.
|
||||
* @param options.params The optional params to pass when connecting.
|
||||
* @param options.headers Deprecated: headers cannot be set on websocket connections and this option will be removed in the future.
|
||||
* @param options.heartbeatIntervalMs The millisec interval to send a heartbeat message.
|
||||
* @param options.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))
|
||||
* @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.
|
||||
* @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 = '';
|
||||
/** @deprecated headers cannot be set on websocket connections */
|
||||
this.headers = {};
|
||||
this.params = {};
|
||||
this.timeout = constants_1.DEFAULT_TIMEOUT;
|
||||
this.transport = null;
|
||||
this.heartbeatIntervalMs = CONNECTION_TIMEOUTS.HEARTBEAT_INTERVAL;
|
||||
this.heartbeatTimer = undefined;
|
||||
this.pendingHeartbeatRef = null;
|
||||
this.heartbeatCallback = noop;
|
||||
this.ref = 0;
|
||||
this.reconnectTimer = null;
|
||||
this.vsn = constants_1.DEFAULT_VSN;
|
||||
this.logger = noop;
|
||||
this.conn = null;
|
||||
this.sendBuffer = [];
|
||||
this.serializer = new serializer_1.default();
|
||||
this.stateChangeCallbacks = {
|
||||
open: [],
|
||||
close: [],
|
||||
error: [],
|
||||
message: [],
|
||||
};
|
||||
this.accessToken = null;
|
||||
this._connectionState = 'disconnected';
|
||||
this._wasManualDisconnect = false;
|
||||
this._authPromise = null;
|
||||
this._heartbeatSentAt = null;
|
||||
/**
|
||||
* Use either custom fetch, if provided, or default fetch to make HTTP requests
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
this._resolveFetch = (customFetch) => {
|
||||
if (customFetch) {
|
||||
return (...args) => customFetch(...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)) {
|
||||
throw new Error('API key is required to connect to Realtime');
|
||||
}
|
||||
this.apiKey = options.params.apikey;
|
||||
// Initialize endpoint URLs
|
||||
this.endPoint = `${endPoint}/${constants_1.TRANSPORTS.websocket}`;
|
||||
this.httpEndpoint = (0, transformers_1.httpEndpointURL)(endPoint);
|
||||
this._initializeOptions(options);
|
||||
this._setupReconnectionTimer();
|
||||
this.fetch = this._resolveFetch(options === null || options === void 0 ? void 0 : options.fetch);
|
||||
}
|
||||
/**
|
||||
* Connects the socket, unless already connected.
|
||||
*/
|
||||
connect() {
|
||||
// Skip if already connecting, disconnecting, or connected
|
||||
if (this.isConnecting() ||
|
||||
this.isDisconnecting() ||
|
||||
(this.conn !== null && this.isConnected())) {
|
||||
return;
|
||||
}
|
||||
this._setConnectionState('connecting');
|
||||
// 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
|
||||
this.conn = new this.transport(this.endpointURL());
|
||||
}
|
||||
else {
|
||||
// Try to use native WebSocket
|
||||
try {
|
||||
this.conn = websocket_factory_1.default.createWebSocket(this.endpointURL());
|
||||
}
|
||||
catch (error) {
|
||||
this._setConnectionState('disconnected');
|
||||
const errorMessage = error.message;
|
||||
// Provide helpful error message based on environment
|
||||
if (errorMessage.includes('Node.js')) {
|
||||
throw new Error(`${errorMessage}\n\n` +
|
||||
'To use Realtime in Node.js, you need to provide a WebSocket implementation:\n\n' +
|
||||
'Option 1: Use Node.js 22+ which has native WebSocket support\n' +
|
||||
'Option 2: Install and provide the "ws" package:\n\n' +
|
||||
' npm install ws\n\n' +
|
||||
' import ws from "ws"\n' +
|
||||
' const client = new RealtimeClient(url, {\n' +
|
||||
' ...options,\n' +
|
||||
' transport: ws\n' +
|
||||
' })');
|
||||
}
|
||||
throw new Error(`WebSocket not available: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
this._setupConnectionHandlers();
|
||||
}
|
||||
/**
|
||||
* Returns the URL of the websocket.
|
||||
* @returns string The URL of the websocket.
|
||||
*/
|
||||
endpointURL() {
|
||||
return this._appendParams(this.endPoint, Object.assign({}, this.params, { vsn: this.vsn }));
|
||||
}
|
||||
/**
|
||||
* Disconnects the socket.
|
||||
*
|
||||
* @param code A numeric status code to send on disconnect.
|
||||
* @param reason A custom reason for the disconnect.
|
||||
*/
|
||||
disconnect(code, reason) {
|
||||
if (this.isDisconnecting()) {
|
||||
return;
|
||||
}
|
||||
this._setConnectionState('disconnecting', true);
|
||||
if (this.conn) {
|
||||
// Setup fallback timer to prevent hanging in disconnecting state
|
||||
const fallbackTimer = setTimeout(() => {
|
||||
this._setConnectionState('disconnected');
|
||||
}, 100);
|
||||
this.conn.onclose = () => {
|
||||
clearTimeout(fallbackTimer);
|
||||
this._setConnectionState('disconnected');
|
||||
};
|
||||
// Close the WebSocket connection if 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();
|
||||
}
|
||||
else {
|
||||
this._setConnectionState('disconnected');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns all created channels
|
||||
*/
|
||||
getChannels() {
|
||||
return this.channels;
|
||||
}
|
||||
/**
|
||||
* Unsubscribes and removes a single channel
|
||||
* @param channel A RealtimeChannel instance
|
||||
*/
|
||||
async removeChannel(channel) {
|
||||
const status = await channel.unsubscribe();
|
||||
if (this.channels.length === 0) {
|
||||
this.disconnect();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
/**
|
||||
* Unsubscribes and removes all channels
|
||||
*/
|
||||
async removeAllChannels() {
|
||||
const values_1 = await Promise.all(this.channels.map((channel) => channel.unsubscribe()));
|
||||
this.channels = [];
|
||||
this.disconnect();
|
||||
return values_1;
|
||||
}
|
||||
/**
|
||||
* Logs the message.
|
||||
*
|
||||
* For customized logging, `this.logger` can be overridden.
|
||||
*/
|
||||
log(kind, msg, data) {
|
||||
this.logger(kind, msg, data);
|
||||
}
|
||||
/**
|
||||
* Returns the current state of the socket.
|
||||
*/
|
||||
connectionState() {
|
||||
switch (this.conn && this.conn.readyState) {
|
||||
case constants_1.SOCKET_STATES.connecting:
|
||||
return constants_1.CONNECTION_STATE.Connecting;
|
||||
case constants_1.SOCKET_STATES.open:
|
||||
return constants_1.CONNECTION_STATE.Open;
|
||||
case constants_1.SOCKET_STATES.closing:
|
||||
return constants_1.CONNECTION_STATE.Closing;
|
||||
default:
|
||||
return constants_1.CONNECTION_STATE.Closed;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns `true` is the connection is open.
|
||||
*/
|
||||
isConnected() {
|
||||
return this.connectionState() === constants_1.CONNECTION_STATE.Open;
|
||||
}
|
||||
/**
|
||||
* Returns `true` if the connection is currently connecting.
|
||||
*/
|
||||
isConnecting() {
|
||||
return this._connectionState === 'connecting';
|
||||
}
|
||||
/**
|
||||
* Returns `true` if the connection is currently disconnecting.
|
||||
*/
|
||||
isDisconnecting() {
|
||||
return this._connectionState === 'disconnecting';
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
if (!exists) {
|
||||
const chan = new RealtimeChannel_1.default(`realtime:${topic}`, params, this);
|
||||
this.channels.push(chan);
|
||||
return chan;
|
||||
}
|
||||
else {
|
||||
return exists;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Push out a message if the socket is connected.
|
||||
*
|
||||
* If the socket is not connected, the message gets enqueued within a local buffer, and sent out when a connection is next established.
|
||||
*/
|
||||
push(data) {
|
||||
const { topic, event, payload, ref } = data;
|
||||
const callback = () => {
|
||||
this.encode(data, (result) => {
|
||||
var _a;
|
||||
(_a = this.conn) === null || _a === void 0 ? void 0 : _a.send(result);
|
||||
});
|
||||
};
|
||||
this.log('push', `${topic} ${event} (${ref})`, payload);
|
||||
if (this.isConnected()) {
|
||||
callback();
|
||||
}
|
||||
else {
|
||||
this.sendBuffer.push(callback);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Sets the JWT access token used for channel subscription authorization and Realtime RLS.
|
||||
*
|
||||
* If param is null it will use the `accessToken` callback function or the token set on the client.
|
||||
*
|
||||
* On callback used, it will set the value of the token internal to the client.
|
||||
*
|
||||
* 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);
|
||||
try {
|
||||
await this._authPromise;
|
||||
}
|
||||
finally {
|
||||
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()) {
|
||||
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');
|
||||
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');
|
||||
setTimeout(() => {
|
||||
var _a;
|
||||
if (!this.isConnected()) {
|
||||
(_a = this.reconnectTimer) === null || _a === void 0 ? void 0 : _a.scheduleTimeout();
|
||||
}
|
||||
}, CONNECTION_TIMEOUTS.HEARTBEAT_TIMEOUT_FALLBACK);
|
||||
return;
|
||||
}
|
||||
// Send heartbeat message to server
|
||||
this._heartbeatSentAt = Date.now();
|
||||
this.pendingHeartbeatRef = this._makeRef();
|
||||
this.push({
|
||||
topic: 'phoenix',
|
||||
event: 'heartbeat',
|
||||
payload: {},
|
||||
ref: this.pendingHeartbeatRef,
|
||||
});
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* Flushes send buffer
|
||||
*/
|
||||
flushSendBuffer() {
|
||||
if (this.isConnected() && this.sendBuffer.length > 0) {
|
||||
this.sendBuffer.forEach((callback) => callback());
|
||||
this.sendBuffer = [];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Return the next message ref, accounting for overflows
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_makeRef() {
|
||||
let newRef = this.ref + 1;
|
||||
if (newRef === this.ref) {
|
||||
this.ref = 0;
|
||||
}
|
||||
else {
|
||||
this.ref = newRef;
|
||||
}
|
||||
return this.ref.toString();
|
||||
}
|
||||
/**
|
||||
* Unsubscribe from channels with the specified topic.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_leaveOpenTopic(topic) {
|
||||
let dupChannel = this.channels.find((c) => c.topic === topic && (c._isJoined() || c._isJoining()));
|
||||
if (dupChannel) {
|
||||
this.log('transport', `leaving duplicate topic "${topic}"`);
|
||||
dupChannel.unsubscribe();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Removes a subscription from the socket.
|
||||
*
|
||||
* @param channel An open subscription.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_remove(channel) {
|
||||
this.channels = this.channels.filter((c) => c.topic !== channel.topic);
|
||||
}
|
||||
/** @internal */
|
||||
_onConnMessage(rawMessage) {
|
||||
this.decode(rawMessage.data, (msg) => {
|
||||
// Handle heartbeat responses
|
||||
if (msg.topic === 'phoenix' &&
|
||||
msg.event === 'phx_reply' &&
|
||||
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
|
||||
const { topic, event, payload, ref } = msg;
|
||||
const refString = ref ? `(${ref})` : '';
|
||||
const status = payload.status || '';
|
||||
this.log('receive', `${status} ${topic} ${event} ${refString}`.trim(), payload);
|
||||
// Route message to appropriate channels
|
||||
this.channels
|
||||
.filter((channel) => channel._isMember(topic))
|
||||
.forEach((channel) => channel._trigger(event, payload, ref));
|
||||
this._triggerStateCallbacks('message', msg);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Clear specific timer
|
||||
* @internal
|
||||
*/
|
||||
_clearTimer(timer) {
|
||||
var _a;
|
||||
if (timer === 'heartbeat' && this.heartbeatTimer) {
|
||||
clearInterval(this.heartbeatTimer);
|
||||
this.heartbeatTimer = undefined;
|
||||
}
|
||||
else if (timer === 'reconnect') {
|
||||
(_a = this.reconnectTimer) === null || _a === void 0 ? void 0 : _a.reset();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Clear all timers
|
||||
* @internal
|
||||
*/
|
||||
_clearAllTimers() {
|
||||
this._clearTimer('heartbeat');
|
||||
this._clearTimer('reconnect');
|
||||
}
|
||||
/**
|
||||
* Setup connection handlers for WebSocket events
|
||||
* @internal
|
||||
*/
|
||||
_setupConnectionHandlers() {
|
||||
if (!this.conn)
|
||||
return;
|
||||
// Set binary type if supported (browsers and most WebSocket implementations)
|
||||
if ('binaryType' in this.conn) {
|
||||
;
|
||||
this.conn.binaryType = 'arraybuffer';
|
||||
}
|
||||
this.conn.onopen = () => this._onConnOpen();
|
||||
this.conn.onerror = (error) => this._onConnError(error);
|
||||
this.conn.onmessage = (event) => this._onConnMessage(event);
|
||||
this.conn.onclose = (event) => this._onConnClose(event);
|
||||
if (this.conn.readyState === constants_1.SOCKET_STATES.open) {
|
||||
this._onConnOpen();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Teardown connection and cleanup resources
|
||||
* @internal
|
||||
*/
|
||||
_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;
|
||||
this.conn.onclose = null;
|
||||
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()}`);
|
||||
// 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();
|
||||
}
|
||||
else {
|
||||
if (!this.workerRef) {
|
||||
this._startWorkerHeartbeat();
|
||||
}
|
||||
}
|
||||
this._triggerStateCallbacks('open');
|
||||
}
|
||||
/** @internal */
|
||||
_startHeartbeat() {
|
||||
this.heartbeatTimer && clearInterval(this.heartbeatTimer);
|
||||
this.heartbeatTimer = setInterval(() => this.sendHeartbeat(), this.heartbeatIntervalMs);
|
||||
}
|
||||
/** @internal */
|
||||
_startWorkerHeartbeat() {
|
||||
if (this.workerUrl) {
|
||||
this.log('worker', `starting worker for from ${this.workerUrl}`);
|
||||
}
|
||||
else {
|
||||
this.log('worker', `starting default worker`);
|
||||
}
|
||||
const objectUrl = this._workerObjectUrl(this.workerUrl);
|
||||
this.workerRef = new Worker(objectUrl);
|
||||
this.workerRef.onerror = (error) => {
|
||||
this.log('worker', 'worker error', error.message);
|
||||
this._terminateWorker();
|
||||
};
|
||||
this.workerRef.onmessage = (event) => {
|
||||
if (event.data.event === 'keepAlive') {
|
||||
this.sendHeartbeat();
|
||||
}
|
||||
};
|
||||
this.workerRef.postMessage({
|
||||
event: 'start',
|
||||
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;
|
||||
this._setConnectionState('disconnected');
|
||||
this.log('transport', 'close', event);
|
||||
this._triggerChanError();
|
||||
this._clearTimer('heartbeat');
|
||||
// Only schedule reconnection if it wasn't a manual disconnect
|
||||
if (!this._wasManualDisconnect) {
|
||||
(_a = this.reconnectTimer) === null || _a === void 0 ? void 0 : _a.scheduleTimeout();
|
||||
}
|
||||
this._triggerStateCallbacks('close', event);
|
||||
}
|
||||
/** @internal */
|
||||
_onConnError(error) {
|
||||
this._setConnectionState('disconnected');
|
||||
this.log('transport', `${error}`);
|
||||
this._triggerChanError();
|
||||
this._triggerStateCallbacks('error', error);
|
||||
try {
|
||||
this.heartbeatCallback('error');
|
||||
}
|
||||
catch (e) {
|
||||
this.log('error', 'error in heartbeat callback', e);
|
||||
}
|
||||
}
|
||||
/** @internal */
|
||||
_triggerChanError() {
|
||||
this.channels.forEach((channel) => channel._trigger(constants_1.CHANNEL_EVENTS.error));
|
||||
}
|
||||
/** @internal */
|
||||
_appendParams(url, params) {
|
||||
if (Object.keys(params).length === 0) {
|
||||
return url;
|
||||
}
|
||||
const prefix = url.match(/\?/) ? '&' : '?';
|
||||
const query = new URLSearchParams(params);
|
||||
return `${url}${prefix}${query}`;
|
||||
}
|
||||
_workerObjectUrl(url) {
|
||||
let result_url;
|
||||
if (url) {
|
||||
result_url = url;
|
||||
}
|
||||
else {
|
||||
const blob = new Blob([WORKER_SCRIPT], { type: 'application/javascript' });
|
||||
result_url = URL.createObjectURL(blob);
|
||||
}
|
||||
return result_url;
|
||||
}
|
||||
/**
|
||||
* Set connection state with proper state management
|
||||
* @internal
|
||||
*/
|
||||
_setConnectionState(state, manual = false) {
|
||||
this._connectionState = state;
|
||||
if (state === 'connecting') {
|
||||
this._wasManualDisconnect = false;
|
||||
}
|
||||
else if (state === 'disconnecting') {
|
||||
this._wasManualDisconnect = manual;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Perform the actual auth operation
|
||||
* @internal
|
||||
*/
|
||||
async _performAuth(token = null) {
|
||||
let tokenToSend;
|
||||
let isManualToken = false;
|
||||
if (token) {
|
||||
tokenToSend = token;
|
||||
// Track if this is a manually-provided token
|
||||
isManualToken = true;
|
||||
}
|
||||
else if (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) => {
|
||||
const payload = {
|
||||
access_token: tokenToSend,
|
||||
version: constants_1.DEFAULT_VERSION,
|
||||
};
|
||||
tokenToSend && channel.updateJoinPayload(payload);
|
||||
if (channel.joinedOnce && channel._isJoined()) {
|
||||
channel._push(constants_1.CHANNEL_EVENTS.access_token, {
|
||||
access_token: tokenToSend,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Wait for any in-flight auth operations to complete
|
||||
* @internal
|
||||
*/
|
||||
async _waitForAuthIfNeeded() {
|
||||
if (this._authPromise) {
|
||||
await this._authPromise;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Safely call setAuth with standardized error handling
|
||||
* @internal
|
||||
*/
|
||||
_setAuthSafely(context = 'general') {
|
||||
// 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
|
||||
* @internal
|
||||
*/
|
||||
_triggerStateCallbacks(event, data) {
|
||||
try {
|
||||
this.stateChangeCallbacks[event].forEach((callback) => {
|
||||
try {
|
||||
callback(data);
|
||||
}
|
||||
catch (e) {
|
||||
this.log('error', `error in ${event} callback`, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
this.log('error', `error triggering ${event} callbacks`, e);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Setup reconnection timer with proper configuration
|
||||
* @internal
|
||||
*/
|
||||
_setupReconnectionTimer() {
|
||||
this.reconnectTimer = new timer_1.default(async () => {
|
||||
setTimeout(async () => {
|
||||
await this._waitForAuthIfNeeded();
|
||||
if (!this.isConnected()) {
|
||||
this.connect();
|
||||
}
|
||||
}, CONNECTION_TIMEOUTS.RECONNECT_DELAY);
|
||||
}, this.reconnectAfterMs);
|
||||
}
|
||||
/**
|
||||
* Initialize client options with defaults
|
||||
* @internal
|
||||
*/
|
||||
_initializeOptions(options) {
|
||||
var _a, _b, _c, _d, _e, _f, _g, _h, _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;
|
||||
this.heartbeatIntervalMs =
|
||||
(_c = options === null || options === void 0 ? void 0 : options.heartbeatIntervalMs) !== null && _c !== void 0 ? _c : CONNECTION_TIMEOUTS.HEARTBEAT_INTERVAL;
|
||||
this.worker = (_d = options === null || options === void 0 ? void 0 : options.worker) !== null && _d !== void 0 ? _d : false;
|
||||
this.accessToken = (_e = options === null || options === void 0 ? void 0 : options.accessToken) !== null && _e !== void 0 ? _e : null;
|
||||
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;
|
||||
if (options === null || options === void 0 ? void 0 : options.logger)
|
||||
this.logger = options.logger;
|
||||
if ((options === null || options === void 0 ? void 0 : options.logLevel) || (options === null || options === void 0 ? void 0 : options.log_level)) {
|
||||
this.logLevel = options.logLevel || options.log_level;
|
||||
this.params = Object.assign(Object.assign({}, this.params), { log_level: this.logLevel });
|
||||
}
|
||||
// Set up functions with defaults
|
||||
this.reconnectAfterMs =
|
||||
(_h = options === null || options === void 0 ? void 0 : options.reconnectAfterMs) !== null && _h !== void 0 ? _h : ((tries) => {
|
||||
return RECONNECT_INTERVALS[tries - 1] || DEFAULT_RECONNECT_FALLBACK;
|
||||
});
|
||||
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) {
|
||||
throw new Error('Web Worker is not supported');
|
||||
}
|
||||
this.workerUrl = options === null || options === void 0 ? void 0 : options.workerUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.default = RealtimeClient;
|
||||
//# sourceMappingURL=RealtimeClient.js.map
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
"use strict";
|
||||
/*
|
||||
This file draws heavily from https://github.com/phoenixframework/phoenix/blob/d344ec0a732ab4ee204215b31de69cf4be72e3bf/assets/js/phoenix/presence.js
|
||||
License: https://github.com/phoenixframework/phoenix/blob/d344ec0a732ab4ee204215b31de69cf4be72e3bf/LICENSE.md
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.REALTIME_PRESENCE_LISTEN_EVENTS = void 0;
|
||||
var REALTIME_PRESENCE_LISTEN_EVENTS;
|
||||
(function (REALTIME_PRESENCE_LISTEN_EVENTS) {
|
||||
REALTIME_PRESENCE_LISTEN_EVENTS["SYNC"] = "sync";
|
||||
REALTIME_PRESENCE_LISTEN_EVENTS["JOIN"] = "join";
|
||||
REALTIME_PRESENCE_LISTEN_EVENTS["LEAVE"] = "leave";
|
||||
})(REALTIME_PRESENCE_LISTEN_EVENTS || (exports.REALTIME_PRESENCE_LISTEN_EVENTS = REALTIME_PRESENCE_LISTEN_EVENTS = {}));
|
||||
class RealtimePresence {
|
||||
/**
|
||||
* Creates a Presence helper that keeps the local presence state in sync with the server.
|
||||
*
|
||||
* @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;
|
||||
this.state = {};
|
||||
this.pendingDiffs = [];
|
||||
this.joinRef = null;
|
||||
this.enabled = false;
|
||||
this.caller = {
|
||||
onJoin: () => { },
|
||||
onLeave: () => { },
|
||||
onSync: () => { },
|
||||
};
|
||||
const events = (opts === null || opts === void 0 ? void 0 : opts.events) || {
|
||||
state: 'presence_state',
|
||||
diff: 'presence_diff',
|
||||
};
|
||||
this.channel._on(events.state, {}, (newState) => {
|
||||
const { onJoin, onLeave, onSync } = this.caller;
|
||||
this.joinRef = this.channel._joinRef();
|
||||
this.state = RealtimePresence.syncState(this.state, newState, onJoin, onLeave);
|
||||
this.pendingDiffs.forEach((diff) => {
|
||||
this.state = RealtimePresence.syncDiff(this.state, diff, onJoin, onLeave);
|
||||
});
|
||||
this.pendingDiffs = [];
|
||||
onSync();
|
||||
});
|
||||
this.channel._on(events.diff, {}, (diff) => {
|
||||
const { onJoin, onLeave, onSync } = this.caller;
|
||||
if (this.inPendingSyncState()) {
|
||||
this.pendingDiffs.push(diff);
|
||||
}
|
||||
else {
|
||||
this.state = RealtimePresence.syncDiff(this.state, diff, onJoin, onLeave);
|
||||
onSync();
|
||||
}
|
||||
});
|
||||
this.onJoin((key, currentPresences, newPresences) => {
|
||||
this.channel._trigger('presence', {
|
||||
event: 'join',
|
||||
key,
|
||||
currentPresences,
|
||||
newPresences,
|
||||
});
|
||||
});
|
||||
this.onLeave((key, currentPresences, leftPresences) => {
|
||||
this.channel._trigger('presence', {
|
||||
event: 'leave',
|
||||
key,
|
||||
currentPresences,
|
||||
leftPresences,
|
||||
});
|
||||
});
|
||||
this.onSync(() => {
|
||||
this.channel._trigger('presence', { event: 'sync' });
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Used to sync the list of presences on the server with the
|
||||
* client's state.
|
||||
*
|
||||
* An optional `onJoin` and `onLeave` callback can be provided to
|
||||
* react to changes in the client's local presences across
|
||||
* disconnects and reconnects with the server.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static syncState(currentState, newState, onJoin, onLeave) {
|
||||
const state = this.cloneDeep(currentState);
|
||||
const transformedState = this.transformState(newState);
|
||||
const joins = {};
|
||||
const leaves = {};
|
||||
this.map(state, (key, presences) => {
|
||||
if (!transformedState[key]) {
|
||||
leaves[key] = presences;
|
||||
}
|
||||
});
|
||||
this.map(transformedState, (key, newPresences) => {
|
||||
const currentPresences = state[key];
|
||||
if (currentPresences) {
|
||||
const newPresenceRefs = newPresences.map((m) => m.presence_ref);
|
||||
const curPresenceRefs = currentPresences.map((m) => m.presence_ref);
|
||||
const joinedPresences = newPresences.filter((m) => curPresenceRefs.indexOf(m.presence_ref) < 0);
|
||||
const leftPresences = currentPresences.filter((m) => newPresenceRefs.indexOf(m.presence_ref) < 0);
|
||||
if (joinedPresences.length > 0) {
|
||||
joins[key] = joinedPresences;
|
||||
}
|
||||
if (leftPresences.length > 0) {
|
||||
leaves[key] = leftPresences;
|
||||
}
|
||||
}
|
||||
else {
|
||||
joins[key] = newPresences;
|
||||
}
|
||||
});
|
||||
return this.syncDiff(state, { joins, leaves }, onJoin, onLeave);
|
||||
}
|
||||
/**
|
||||
* Used to sync a diff of presence join and leave events from the
|
||||
* server, as they happen.
|
||||
*
|
||||
* Like `syncState`, `syncDiff` accepts optional `onJoin` and
|
||||
* `onLeave` callbacks to react to a user joining or leaving from a
|
||||
* device.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static syncDiff(state, diff, onJoin, onLeave) {
|
||||
const { joins, leaves } = {
|
||||
joins: this.transformState(diff.joins),
|
||||
leaves: this.transformState(diff.leaves),
|
||||
};
|
||||
if (!onJoin) {
|
||||
onJoin = () => { };
|
||||
}
|
||||
if (!onLeave) {
|
||||
onLeave = () => { };
|
||||
}
|
||||
this.map(joins, (key, newPresences) => {
|
||||
var _a;
|
||||
const currentPresences = (_a = state[key]) !== null && _a !== void 0 ? _a : [];
|
||||
state[key] = this.cloneDeep(newPresences);
|
||||
if (currentPresences.length > 0) {
|
||||
const joinedPresenceRefs = state[key].map((m) => m.presence_ref);
|
||||
const curPresences = currentPresences.filter((m) => joinedPresenceRefs.indexOf(m.presence_ref) < 0);
|
||||
state[key].unshift(...curPresences);
|
||||
}
|
||||
onJoin(key, currentPresences, newPresences);
|
||||
});
|
||||
this.map(leaves, (key, leftPresences) => {
|
||||
let currentPresences = state[key];
|
||||
if (!currentPresences)
|
||||
return;
|
||||
const presenceRefsToRemove = leftPresences.map((m) => m.presence_ref);
|
||||
currentPresences = currentPresences.filter((m) => presenceRefsToRemove.indexOf(m.presence_ref) < 0);
|
||||
state[key] = currentPresences;
|
||||
onLeave(key, currentPresences, leftPresences);
|
||||
if (currentPresences.length === 0)
|
||||
delete state[key];
|
||||
});
|
||||
return state;
|
||||
}
|
||||
/** @internal */
|
||||
static map(obj, func) {
|
||||
return Object.getOwnPropertyNames(obj).map((key) => func(key, obj[key]));
|
||||
}
|
||||
/**
|
||||
* Remove 'metas' key
|
||||
* Change 'phx_ref' to 'presence_ref'
|
||||
* Remove 'phx_ref' and 'phx_ref_prev'
|
||||
*
|
||||
* @example
|
||||
* // returns {
|
||||
* abc123: [
|
||||
* { presence_ref: '2', user_id: 1 },
|
||||
* { presence_ref: '3', user_id: 2 }
|
||||
* ]
|
||||
* }
|
||||
* RealtimePresence.transformState({
|
||||
* abc123: {
|
||||
* metas: [
|
||||
* { phx_ref: '2', phx_ref_prev: '1' user_id: 1 },
|
||||
* { phx_ref: '3', user_id: 2 }
|
||||
* ]
|
||||
* }
|
||||
* })
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static transformState(state) {
|
||||
state = this.cloneDeep(state);
|
||||
return Object.getOwnPropertyNames(state).reduce((newState, key) => {
|
||||
const presences = state[key];
|
||||
if ('metas' in presences) {
|
||||
newState[key] = presences.metas.map((presence) => {
|
||||
presence['presence_ref'] = presence['phx_ref'];
|
||||
delete presence['phx_ref'];
|
||||
delete presence['phx_ref_prev'];
|
||||
return presence;
|
||||
});
|
||||
}
|
||||
else {
|
||||
newState[key] = presences;
|
||||
}
|
||||
return newState;
|
||||
}, {});
|
||||
}
|
||||
/** @internal */
|
||||
static cloneDeep(obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
/** @internal */
|
||||
onJoin(callback) {
|
||||
this.caller.onJoin = callback;
|
||||
}
|
||||
/** @internal */
|
||||
onLeave(callback) {
|
||||
this.caller.onLeave = callback;
|
||||
}
|
||||
/** @internal */
|
||||
onSync(callback) {
|
||||
this.caller.onSync = callback;
|
||||
}
|
||||
/** @internal */
|
||||
inPendingSyncState() {
|
||||
return !this.joinRef || this.joinRef !== this.channel._joinRef();
|
||||
}
|
||||
}
|
||||
exports.default = RealtimePresence;
|
||||
//# sourceMappingURL=RealtimePresence.js.map
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
"use strict";
|
||||
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 tslib_1 = require("tslib");
|
||||
const RealtimeClient_1 = tslib_1.__importDefault(require("./RealtimeClient"));
|
||||
exports.RealtimeClient = RealtimeClient_1.default;
|
||||
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 = 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 = tslib_1.__importDefault(require("./lib/websocket-factory"));
|
||||
exports.WebSocketFactory = websocket_factory_1.default;
|
||||
//# sourceMappingURL=index.js.map
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
"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.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 = '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;
|
||||
exports.MAX_PUSH_BUFFER_SIZE = 100;
|
||||
var SOCKET_STATES;
|
||||
(function (SOCKET_STATES) {
|
||||
SOCKET_STATES[SOCKET_STATES["connecting"] = 0] = "connecting";
|
||||
SOCKET_STATES[SOCKET_STATES["open"] = 1] = "open";
|
||||
SOCKET_STATES[SOCKET_STATES["closing"] = 2] = "closing";
|
||||
SOCKET_STATES[SOCKET_STATES["closed"] = 3] = "closed";
|
||||
})(SOCKET_STATES || (exports.SOCKET_STATES = SOCKET_STATES = {}));
|
||||
var CHANNEL_STATES;
|
||||
(function (CHANNEL_STATES) {
|
||||
CHANNEL_STATES["closed"] = "closed";
|
||||
CHANNEL_STATES["errored"] = "errored";
|
||||
CHANNEL_STATES["joined"] = "joined";
|
||||
CHANNEL_STATES["joining"] = "joining";
|
||||
CHANNEL_STATES["leaving"] = "leaving";
|
||||
})(CHANNEL_STATES || (exports.CHANNEL_STATES = CHANNEL_STATES = {}));
|
||||
var CHANNEL_EVENTS;
|
||||
(function (CHANNEL_EVENTS) {
|
||||
CHANNEL_EVENTS["close"] = "phx_close";
|
||||
CHANNEL_EVENTS["error"] = "phx_error";
|
||||
CHANNEL_EVENTS["join"] = "phx_join";
|
||||
CHANNEL_EVENTS["reply"] = "phx_reply";
|
||||
CHANNEL_EVENTS["leave"] = "phx_leave";
|
||||
CHANNEL_EVENTS["access_token"] = "access_token";
|
||||
})(CHANNEL_EVENTS || (exports.CHANNEL_EVENTS = CHANNEL_EVENTS = {}));
|
||||
var TRANSPORTS;
|
||||
(function (TRANSPORTS) {
|
||||
TRANSPORTS["websocket"] = "websocket";
|
||||
})(TRANSPORTS || (exports.TRANSPORTS = TRANSPORTS = {}));
|
||||
var CONNECTION_STATE;
|
||||
(function (CONNECTION_STATE) {
|
||||
CONNECTION_STATE["Connecting"] = "connecting";
|
||||
CONNECTION_STATE["Open"] = "open";
|
||||
CONNECTION_STATE["Closing"] = "closing";
|
||||
CONNECTION_STATE["Closed"] = "closed";
|
||||
})(CONNECTION_STATE || (exports.CONNECTION_STATE = CONNECTION_STATE = {}));
|
||||
//# sourceMappingURL=constants.js.map
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
"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
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
class Serializer {
|
||||
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 (this._isArrayBuffer(rawPayload)) {
|
||||
let result = this._binaryDecode(rawPayload);
|
||||
return callback(result);
|
||||
}
|
||||
if (typeof rawPayload === 'string') {
|
||||
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();
|
||||
switch (kind) {
|
||||
case this.KINDS.userBroadcast:
|
||||
return this._decodeUserBroadcast(buffer, view, decoder);
|
||||
}
|
||||
}
|
||||
_decodeUserBroadcast(buffer, view, decoder) {
|
||||
const topicSize = view.getUint8(1);
|
||||
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 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;
|
||||
//# sourceMappingURL=serializer.js.map
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
/**
|
||||
* Creates a timer that accepts a `timerCalc` function to perform calculated timeout retries, such as exponential backoff.
|
||||
*
|
||||
* @example
|
||||
* let reconnectTimer = new Timer(() => this.connect(), function(tries){
|
||||
* return [1000, 5000, 10000][tries - 1] || 10000
|
||||
* })
|
||||
* reconnectTimer.scheduleTimeout() // fires after 1000
|
||||
* reconnectTimer.scheduleTimeout() // fires after 5000
|
||||
* reconnectTimer.reset()
|
||||
* reconnectTimer.scheduleTimeout() // fires after 1000
|
||||
*/
|
||||
class Timer {
|
||||
constructor(callback, timerCalc) {
|
||||
this.callback = callback;
|
||||
this.timerCalc = timerCalc;
|
||||
this.timer = undefined;
|
||||
this.tries = 0;
|
||||
this.callback = callback;
|
||||
this.timerCalc = timerCalc;
|
||||
}
|
||||
reset() {
|
||||
this.tries = 0;
|
||||
clearTimeout(this.timer);
|
||||
this.timer = undefined;
|
||||
}
|
||||
// Cancels any previous scheduleTimeout and schedules callback
|
||||
scheduleTimeout() {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => {
|
||||
this.tries = this.tries + 1;
|
||||
this.callback();
|
||||
}, this.timerCalc(this.tries + 1));
|
||||
}
|
||||
}
|
||||
exports.default = Timer;
|
||||
//# sourceMappingURL=timer.js.map
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Helpers to convert the change Payload into native JS types.
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.httpEndpointURL = exports.toTimestampString = exports.toArray = exports.toJson = exports.toNumber = exports.toBoolean = exports.convertCell = exports.convertColumn = exports.convertChangeData = exports.PostgresTypes = void 0;
|
||||
// Adapted from epgsql (src/epgsql_binary.erl), this module licensed under
|
||||
// 3-clause BSD found here: https://raw.githubusercontent.com/epgsql/epgsql/devel/LICENSE
|
||||
var PostgresTypes;
|
||||
(function (PostgresTypes) {
|
||||
PostgresTypes["abstime"] = "abstime";
|
||||
PostgresTypes["bool"] = "bool";
|
||||
PostgresTypes["date"] = "date";
|
||||
PostgresTypes["daterange"] = "daterange";
|
||||
PostgresTypes["float4"] = "float4";
|
||||
PostgresTypes["float8"] = "float8";
|
||||
PostgresTypes["int2"] = "int2";
|
||||
PostgresTypes["int4"] = "int4";
|
||||
PostgresTypes["int4range"] = "int4range";
|
||||
PostgresTypes["int8"] = "int8";
|
||||
PostgresTypes["int8range"] = "int8range";
|
||||
PostgresTypes["json"] = "json";
|
||||
PostgresTypes["jsonb"] = "jsonb";
|
||||
PostgresTypes["money"] = "money";
|
||||
PostgresTypes["numeric"] = "numeric";
|
||||
PostgresTypes["oid"] = "oid";
|
||||
PostgresTypes["reltime"] = "reltime";
|
||||
PostgresTypes["text"] = "text";
|
||||
PostgresTypes["time"] = "time";
|
||||
PostgresTypes["timestamp"] = "timestamp";
|
||||
PostgresTypes["timestamptz"] = "timestamptz";
|
||||
PostgresTypes["timetz"] = "timetz";
|
||||
PostgresTypes["tsrange"] = "tsrange";
|
||||
PostgresTypes["tstzrange"] = "tstzrange";
|
||||
})(PostgresTypes || (exports.PostgresTypes = PostgresTypes = {}));
|
||||
/**
|
||||
* Takes an array of columns and an object of string values then converts each string value
|
||||
* to its mapped type.
|
||||
*
|
||||
* @param {{name: String, type: String}[]} columns
|
||||
* @param {Object} record
|
||||
* @param {Object} options The map of various options that can be applied to the mapper
|
||||
* @param {Array} options.skipTypes The array of types that should not be converted
|
||||
*
|
||||
* @example convertChangeData([{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age:'33'}, {})
|
||||
* //=>{ first_name: 'Paul', age: 33 }
|
||||
*/
|
||||
const convertChangeData = (columns, record, options = {}) => {
|
||||
var _a;
|
||||
const skipTypes = (_a = options.skipTypes) !== null && _a !== void 0 ? _a : [];
|
||||
if (!record) {
|
||||
return {};
|
||||
}
|
||||
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 (_a) {
|
||||
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) => {
|
||||
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
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.version = void 0;
|
||||
// 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
+131
@@ -0,0 +1,131 @@
|
||||
"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') {
|
||||
return { type: 'native', constructor: globalThis.WebSocket };
|
||||
}
|
||||
if (typeof global !== 'undefined' && typeof global.WebSocket !== 'undefined') {
|
||||
return { type: 'native', constructor: global.WebSocket };
|
||||
}
|
||||
if (typeof globalThis !== 'undefined' &&
|
||||
typeof globalThis.WebSocketPair !== 'undefined' &&
|
||||
typeof globalThis.WebSocket === 'undefined') {
|
||||
return {
|
||||
type: 'cloudflare',
|
||||
error: 'Cloudflare Workers detected. WebSocket clients are not supported in Cloudflare Workers.',
|
||||
workaround: 'Use Cloudflare Workers WebSocket API for server-side WebSocket handling, or deploy to a different runtime.',
|
||||
};
|
||||
}
|
||||
if ((typeof globalThis !== 'undefined' && globalThis.EdgeRuntime) ||
|
||||
(typeof navigator !== 'undefined' && ((_a = navigator.userAgent) === null || _a === void 0 ? void 0 : _a.includes('Vercel-Edge')))) {
|
||||
return {
|
||||
type: 'unsupported',
|
||||
error: 'Edge runtime detected (Vercel Edge/Netlify Edge). WebSockets are not supported in edge functions.',
|
||||
workaround: 'Use serverless functions or a different deployment target for WebSocket functionality.',
|
||||
};
|
||||
}
|
||||
// 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.',
|
||||
};
|
||||
}
|
||||
// Node.js < 22 doesn't have native WebSocket
|
||||
return {
|
||||
type: 'unsupported',
|
||||
error: `Node.js ${nodeVersion} detected without native WebSocket support.`,
|
||||
workaround: 'For Node.js < 22, install "ws" package and provide it via the transport option:\n' +
|
||||
'import ws from "ws"\n' +
|
||||
'new RealtimeClient(url, { transport: ws })',
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: 'unsupported',
|
||||
error: 'Unknown JavaScript runtime without WebSocket support.',
|
||||
workaround: "Ensure you're running in a supported environment (browser, Node.js, Deno) or provide a custom WebSocket implementation.",
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 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) {
|
||||
return env.constructor;
|
||||
}
|
||||
let errorMessage = env.error || 'WebSocket not supported in this environment.';
|
||||
if (env.workaround) {
|
||||
errorMessage += `\n\nSuggested solution: ${env.workaround}`;
|
||||
}
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
/**
|
||||
* 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();
|
||||
return env.type === 'native' || env.type === 'ws';
|
||||
}
|
||||
catch (_a) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.WebSocketFactory = WebSocketFactory;
|
||||
exports.default = WebSocketFactory;
|
||||
//# sourceMappingURL=websocket-factory.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../../.nitro/tslib@2.8.1
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "@supabase/realtime-js",
|
||||
"version": "2.93.3",
|
||||
"description": "Listen to realtime updates to your PostgreSQL database",
|
||||
"keywords": [
|
||||
"realtime",
|
||||
"phoenix",
|
||||
"elixir",
|
||||
"javascript",
|
||||
"typescript",
|
||||
"firebase",
|
||||
"supabase"
|
||||
],
|
||||
"homepage": "https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js",
|
||||
"bugs": "https://github.com/supabase/supabase-js/issues",
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"main": "dist/main/index.js",
|
||||
"module": "dist/module/index.js",
|
||||
"types": "dist/module/index.d.ts",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/supabase/supabase-js.git",
|
||||
"directory": "packages/core/realtime-js"
|
||||
},
|
||||
"author": "Supabase",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"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",
|
||||
"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 .",
|
||||
"test:ci": "vitest run --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/phoenix": "^1.6.6",
|
||||
"@types/ws": "^8.18.1",
|
||||
"tslib": "2.8.1",
|
||||
"ws": "^8.18.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitest/coverage-v8": "^3.1.4",
|
||||
"esm": "^3.2.25",
|
||||
"jsdom": "^16.7.0",
|
||||
"jsdom-global": "3.0.0",
|
||||
"mock-socket": "^9.3.1",
|
||||
"nyc": "^15.1.0",
|
||||
"web-worker": "1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
}
|
||||
+2616
File diff suppressed because it is too large
Load Diff
+62
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "@supabase/storage-js",
|
||||
"version": "2.93.3",
|
||||
"description": "Isomorphic storage client for Supabase.",
|
||||
"keywords": [
|
||||
"javascript",
|
||||
"typescript",
|
||||
"supabase"
|
||||
],
|
||||
"homepage": "https://github.com/supabase/supabase-js/tree/master/packages/core/storage-js",
|
||||
"bugs": "https://github.com/supabase/supabase-js/issues",
|
||||
"license": "MIT",
|
||||
"author": "Supabase",
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "dist/index.d.cts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/index.d.mts",
|
||||
"default": "./dist/index.mjs"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/index.d.cts",
|
||||
"default": "./dist/index.cjs"
|
||||
}
|
||||
},
|
||||
"./dist/*": "./dist/*",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/supabase/supabase-js.git",
|
||||
"directory": "packages/core/storage-js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsdown",
|
||||
"build:watch": "tsdown --watch",
|
||||
"docs": "typedoc --entryPoints src/index.ts --out docs/v2 --entryPoints src/packages/* --excludePrivate --excludeProtected",
|
||||
"docs:json": "typedoc --json docs/v2/spec.json --entryPoints src/index.ts --entryPoints src/packages/* --excludePrivate --excludeExternals --excludeProtected"
|
||||
},
|
||||
"dependencies": {
|
||||
"iceberg-js": "^0.8.1",
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"form-data": "^4.0.0"
|
||||
},
|
||||
"jsdelivr": "dist/umd/supabase.js",
|
||||
"unpkg": "dist/umd/supabase.js",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
}
|
||||
+406
@@ -0,0 +1,406 @@
|
||||
import { FunctionRegion, FunctionsClient, FunctionsError, FunctionsFetchError, FunctionsHttpError, FunctionsRelayError } from "@supabase/functions-js";
|
||||
import { PostgrestClient, PostgrestError } from "@supabase/postgrest-js";
|
||||
import { RealtimeClient } from "@supabase/realtime-js";
|
||||
import { StorageClient } from "@supabase/storage-js";
|
||||
import { AuthClient } from "@supabase/auth-js";
|
||||
|
||||
export * from "@supabase/realtime-js"
|
||||
|
||||
export * from "@supabase/auth-js"
|
||||
|
||||
//#region src/lib/version.ts
|
||||
const version = "2.93.3";
|
||||
|
||||
//#endregion
|
||||
//#region src/lib/constants.ts
|
||||
let JS_ENV = "";
|
||||
if (typeof Deno !== "undefined") JS_ENV = "deno";
|
||||
else if (typeof document !== "undefined") JS_ENV = "web";
|
||||
else if (typeof navigator !== "undefined" && navigator.product === "ReactNative") JS_ENV = "react-native";
|
||||
else JS_ENV = "node";
|
||||
const DEFAULT_HEADERS = { "X-Client-Info": `supabase-js-${JS_ENV}/${version}` };
|
||||
const DEFAULT_GLOBAL_OPTIONS = { headers: DEFAULT_HEADERS };
|
||||
const DEFAULT_DB_OPTIONS = { schema: "public" };
|
||||
const DEFAULT_AUTH_OPTIONS = {
|
||||
autoRefreshToken: true,
|
||||
persistSession: true,
|
||||
detectSessionInUrl: true,
|
||||
flowType: "implicit"
|
||||
};
|
||||
const DEFAULT_REALTIME_OPTIONS = {};
|
||||
|
||||
//#endregion
|
||||
//#region \0@oxc-project+runtime@0.101.0/helpers/typeof.js
|
||||
function _typeof(o) {
|
||||
"@babel/helpers - typeof";
|
||||
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o$1) {
|
||||
return typeof o$1;
|
||||
} : function(o$1) {
|
||||
return o$1 && "function" == typeof Symbol && o$1.constructor === Symbol && o$1 !== Symbol.prototype ? "symbol" : typeof o$1;
|
||||
}, _typeof(o);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
//#region \0@oxc-project+runtime@0.101.0/helpers/toPrimitive.js
|
||||
function toPrimitive(t, r) {
|
||||
if ("object" != _typeof(t) || !t) return t;
|
||||
var e = t[Symbol.toPrimitive];
|
||||
if (void 0 !== e) {
|
||||
var i = e.call(t, r || "default");
|
||||
if ("object" != _typeof(i)) return i;
|
||||
throw new TypeError("@@toPrimitive must return a primitive value.");
|
||||
}
|
||||
return ("string" === r ? String : Number)(t);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
//#region \0@oxc-project+runtime@0.101.0/helpers/toPropertyKey.js
|
||||
function toPropertyKey(t) {
|
||||
var i = toPrimitive(t, "string");
|
||||
return "symbol" == _typeof(i) ? i : i + "";
|
||||
}
|
||||
|
||||
//#endregion
|
||||
//#region \0@oxc-project+runtime@0.101.0/helpers/defineProperty.js
|
||||
function _defineProperty(e, r, t) {
|
||||
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
||||
value: t,
|
||||
enumerable: !0,
|
||||
configurable: !0,
|
||||
writable: !0
|
||||
}) : e[r] = t, e;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
//#region \0@oxc-project+runtime@0.101.0/helpers/objectSpread2.js
|
||||
function ownKeys(e, r) {
|
||||
var t = Object.keys(e);
|
||||
if (Object.getOwnPropertySymbols) {
|
||||
var o = Object.getOwnPropertySymbols(e);
|
||||
r && (o = o.filter(function(r$1) {
|
||||
return Object.getOwnPropertyDescriptor(e, r$1).enumerable;
|
||||
})), t.push.apply(t, o);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
function _objectSpread2(e) {
|
||||
for (var r = 1; r < arguments.length; r++) {
|
||||
var t = null != arguments[r] ? arguments[r] : {};
|
||||
r % 2 ? ownKeys(Object(t), !0).forEach(function(r$1) {
|
||||
_defineProperty(e, r$1, t[r$1]);
|
||||
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function(r$1) {
|
||||
Object.defineProperty(e, r$1, Object.getOwnPropertyDescriptor(t, r$1));
|
||||
});
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
//#region src/lib/fetch.ts
|
||||
const resolveFetch = (customFetch) => {
|
||||
if (customFetch) return (...args) => customFetch(...args);
|
||||
return (...args) => fetch(...args);
|
||||
};
|
||||
const resolveHeadersConstructor = () => {
|
||||
return Headers;
|
||||
};
|
||||
const fetchWithAuth = (supabaseKey, getAccessToken, customFetch) => {
|
||||
const fetch$1 = resolveFetch(customFetch);
|
||||
const HeadersConstructor = resolveHeadersConstructor();
|
||||
return async (input, init) => {
|
||||
var _await$getAccessToken;
|
||||
const accessToken = (_await$getAccessToken = await getAccessToken()) !== null && _await$getAccessToken !== void 0 ? _await$getAccessToken : supabaseKey;
|
||||
let headers = new HeadersConstructor(init === null || init === void 0 ? void 0 : init.headers);
|
||||
if (!headers.has("apikey")) headers.set("apikey", supabaseKey);
|
||||
if (!headers.has("Authorization")) headers.set("Authorization", `Bearer ${accessToken}`);
|
||||
return fetch$1(input, _objectSpread2(_objectSpread2({}, init), {}, { headers }));
|
||||
};
|
||||
};
|
||||
|
||||
//#endregion
|
||||
//#region src/lib/helpers.ts
|
||||
function ensureTrailingSlash(url) {
|
||||
return url.endsWith("/") ? url : url + "/";
|
||||
}
|
||||
function applySettingDefaults(options, defaults) {
|
||||
var _DEFAULT_GLOBAL_OPTIO, _globalOptions$header;
|
||||
const { db: dbOptions, auth: authOptions, realtime: realtimeOptions, global: globalOptions } = options;
|
||||
const { db: DEFAULT_DB_OPTIONS$1, auth: DEFAULT_AUTH_OPTIONS$1, realtime: DEFAULT_REALTIME_OPTIONS$1, global: DEFAULT_GLOBAL_OPTIONS$1 } = defaults;
|
||||
const result = {
|
||||
db: _objectSpread2(_objectSpread2({}, DEFAULT_DB_OPTIONS$1), dbOptions),
|
||||
auth: _objectSpread2(_objectSpread2({}, DEFAULT_AUTH_OPTIONS$1), authOptions),
|
||||
realtime: _objectSpread2(_objectSpread2({}, DEFAULT_REALTIME_OPTIONS$1), realtimeOptions),
|
||||
storage: {},
|
||||
global: _objectSpread2(_objectSpread2(_objectSpread2({}, DEFAULT_GLOBAL_OPTIONS$1), globalOptions), {}, { headers: _objectSpread2(_objectSpread2({}, (_DEFAULT_GLOBAL_OPTIO = DEFAULT_GLOBAL_OPTIONS$1 === null || DEFAULT_GLOBAL_OPTIONS$1 === void 0 ? void 0 : DEFAULT_GLOBAL_OPTIONS$1.headers) !== null && _DEFAULT_GLOBAL_OPTIO !== void 0 ? _DEFAULT_GLOBAL_OPTIO : {}), (_globalOptions$header = globalOptions === null || globalOptions === void 0 ? void 0 : globalOptions.headers) !== null && _globalOptions$header !== void 0 ? _globalOptions$header : {}) }),
|
||||
accessToken: async () => ""
|
||||
};
|
||||
if (options.accessToken) result.accessToken = options.accessToken;
|
||||
else delete result.accessToken;
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Validates a Supabase client URL
|
||||
*
|
||||
* @param {string} supabaseUrl - The Supabase client URL string.
|
||||
* @returns {URL} - The validated base URL.
|
||||
* @throws {Error}
|
||||
*/
|
||||
function validateSupabaseUrl(supabaseUrl) {
|
||||
const trimmedUrl = supabaseUrl === null || supabaseUrl === void 0 ? void 0 : supabaseUrl.trim();
|
||||
if (!trimmedUrl) throw new Error("supabaseUrl is required.");
|
||||
if (!trimmedUrl.match(/^https?:\/\//i)) throw new Error("Invalid supabaseUrl: Must be a valid HTTP or HTTPS URL.");
|
||||
try {
|
||||
return new URL(ensureTrailingSlash(trimmedUrl));
|
||||
} catch (_unused) {
|
||||
throw Error("Invalid supabaseUrl: Provided URL is malformed.");
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
//#region src/lib/SupabaseAuthClient.ts
|
||||
var SupabaseAuthClient = class extends AuthClient {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
}
|
||||
};
|
||||
|
||||
//#endregion
|
||||
//#region src/SupabaseClient.ts
|
||||
/**
|
||||
* Supabase Client.
|
||||
*
|
||||
* An isomorphic Javascript client for interacting with Postgres.
|
||||
*/
|
||||
var SupabaseClient = class {
|
||||
/**
|
||||
* Create a new client for use in the browser.
|
||||
* @param supabaseUrl The unique Supabase URL which is supplied when you create a new project in your project dashboard.
|
||||
* @param supabaseKey The unique Supabase Key which is supplied when you create a new project in your project dashboard.
|
||||
* @param options.db.schema You can switch in between schemas. The schema needs to be on the list of exposed schemas inside Supabase.
|
||||
* @param options.auth.autoRefreshToken Set to "true" if you want to automatically refresh the token before expiring.
|
||||
* @param options.auth.persistSession Set to "true" if you want to automatically save the user session into local storage.
|
||||
* @param options.auth.detectSessionInUrl Set to "true" if you want to automatically detects OAuth grants in the URL and signs in the user.
|
||||
* @param options.realtime Options passed along to realtime-js constructor.
|
||||
* @param options.storage Options passed along to the storage-js constructor.
|
||||
* @param options.global.fetch A custom fetch implementation.
|
||||
* @param options.global.headers Any additional headers to send with each network request.
|
||||
* @example
|
||||
* ```ts
|
||||
* import { createClient } from '@supabase/supabase-js'
|
||||
*
|
||||
* const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key')
|
||||
* const { data } = await supabase.from('profiles').select('*')
|
||||
* ```
|
||||
*/
|
||||
constructor(supabaseUrl, supabaseKey, options) {
|
||||
var _settings$auth$storag, _settings$global$head;
|
||||
this.supabaseUrl = supabaseUrl;
|
||||
this.supabaseKey = supabaseKey;
|
||||
const baseUrl = validateSupabaseUrl(supabaseUrl);
|
||||
if (!supabaseKey) throw new Error("supabaseKey is required.");
|
||||
this.realtimeUrl = new URL("realtime/v1", baseUrl);
|
||||
this.realtimeUrl.protocol = this.realtimeUrl.protocol.replace("http", "ws");
|
||||
this.authUrl = new URL("auth/v1", baseUrl);
|
||||
this.storageUrl = new URL("storage/v1", baseUrl);
|
||||
this.functionsUrl = new URL("functions/v1", baseUrl);
|
||||
const defaultStorageKey = `sb-${baseUrl.hostname.split(".")[0]}-auth-token`;
|
||||
const DEFAULTS = {
|
||||
db: DEFAULT_DB_OPTIONS,
|
||||
realtime: DEFAULT_REALTIME_OPTIONS,
|
||||
auth: _objectSpread2(_objectSpread2({}, DEFAULT_AUTH_OPTIONS), {}, { storageKey: defaultStorageKey }),
|
||||
global: DEFAULT_GLOBAL_OPTIONS
|
||||
};
|
||||
const settings = applySettingDefaults(options !== null && options !== void 0 ? options : {}, DEFAULTS);
|
||||
this.storageKey = (_settings$auth$storag = settings.auth.storageKey) !== null && _settings$auth$storag !== void 0 ? _settings$auth$storag : "";
|
||||
this.headers = (_settings$global$head = settings.global.headers) !== null && _settings$global$head !== void 0 ? _settings$global$head : {};
|
||||
if (!settings.accessToken) {
|
||||
var _settings$auth;
|
||||
this.auth = this._initSupabaseAuthClient((_settings$auth = settings.auth) !== null && _settings$auth !== void 0 ? _settings$auth : {}, this.headers, settings.global.fetch);
|
||||
} else {
|
||||
this.accessToken = settings.accessToken;
|
||||
this.auth = new Proxy({}, { get: (_, prop) => {
|
||||
throw new Error(`@supabase/supabase-js: Supabase Client is configured with the accessToken option, accessing supabase.auth.${String(prop)} is not possible`);
|
||||
} });
|
||||
}
|
||||
this.fetch = fetchWithAuth(supabaseKey, this._getAccessToken.bind(this), settings.global.fetch);
|
||||
this.realtime = this._initRealtimeClient(_objectSpread2({
|
||||
headers: this.headers,
|
||||
accessToken: this._getAccessToken.bind(this)
|
||||
}, settings.realtime));
|
||||
if (this.accessToken) Promise.resolve(this.accessToken()).then((token) => this.realtime.setAuth(token)).catch((e) => console.warn("Failed to set initial Realtime auth token:", e));
|
||||
this.rest = new PostgrestClient(new URL("rest/v1", baseUrl).href, {
|
||||
headers: this.headers,
|
||||
schema: settings.db.schema,
|
||||
fetch: this.fetch
|
||||
});
|
||||
this.storage = new StorageClient(this.storageUrl.href, this.headers, this.fetch, options === null || options === void 0 ? void 0 : options.storage);
|
||||
if (!settings.accessToken) this._listenForAuthEvents();
|
||||
}
|
||||
/**
|
||||
* Supabase Functions allows you to deploy and invoke edge functions.
|
||||
*/
|
||||
get functions() {
|
||||
return new FunctionsClient(this.functionsUrl.href, {
|
||||
headers: this.headers,
|
||||
customFetch: this.fetch
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Perform a query on a table or a view.
|
||||
*
|
||||
* @param relation - The table or view name to query
|
||||
*/
|
||||
from(relation) {
|
||||
return this.rest.from(relation);
|
||||
}
|
||||
/**
|
||||
* Select a schema to query or perform an function (rpc) call.
|
||||
*
|
||||
* The schema needs to be on the list of exposed schemas inside Supabase.
|
||||
*
|
||||
* @param schema - The schema to query
|
||||
*/
|
||||
schema(schema) {
|
||||
return this.rest.schema(schema);
|
||||
}
|
||||
/**
|
||||
* Perform a function call.
|
||||
*
|
||||
* @param fn - The function name to call
|
||||
* @param args - The arguments to pass to the function call
|
||||
* @param options - Named parameters
|
||||
* @param options.head - When set to `true`, `data` will not be returned.
|
||||
* Useful if you only need the count.
|
||||
* @param options.get - When set to `true`, the function will be called with
|
||||
* read-only access mode.
|
||||
* @param options.count - Count algorithm to use to count rows returned by the
|
||||
* function. Only applicable for [set-returning
|
||||
* functions](https://www.postgresql.org/docs/current/functions-srf.html).
|
||||
*
|
||||
* `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the
|
||||
* hood.
|
||||
*
|
||||
* `"planned"`: Approximated but fast count algorithm. Uses the Postgres
|
||||
* statistics under the hood.
|
||||
*
|
||||
* `"estimated"`: Uses exact count for low numbers and planned count for high
|
||||
* numbers.
|
||||
*/
|
||||
rpc(fn, args = {}, options = {
|
||||
head: false,
|
||||
get: false,
|
||||
count: void 0
|
||||
}) {
|
||||
return this.rest.rpc(fn, args, options);
|
||||
}
|
||||
/**
|
||||
* Creates a Realtime channel with Broadcast, Presence, and Postgres Changes.
|
||||
*
|
||||
* @param {string} name - The name of the Realtime channel.
|
||||
* @param {Object} opts - The options to pass to the Realtime channel.
|
||||
*
|
||||
*/
|
||||
channel(name, opts = { config: {} }) {
|
||||
return this.realtime.channel(name, opts);
|
||||
}
|
||||
/**
|
||||
* Returns all Realtime channels.
|
||||
*/
|
||||
getChannels() {
|
||||
return this.realtime.getChannels();
|
||||
}
|
||||
/**
|
||||
* Unsubscribes and removes Realtime channel from Realtime client.
|
||||
*
|
||||
* @param {RealtimeChannel} channel - The name of the Realtime channel.
|
||||
*
|
||||
*/
|
||||
removeChannel(channel) {
|
||||
return this.realtime.removeChannel(channel);
|
||||
}
|
||||
/**
|
||||
* Unsubscribes and removes all Realtime channels from Realtime client.
|
||||
*/
|
||||
removeAllChannels() {
|
||||
return this.realtime.removeAllChannels();
|
||||
}
|
||||
async _getAccessToken() {
|
||||
var _this = this;
|
||||
var _data$session$access_, _data$session;
|
||||
if (_this.accessToken) return await _this.accessToken();
|
||||
const { data } = await _this.auth.getSession();
|
||||
return (_data$session$access_ = (_data$session = data.session) === null || _data$session === void 0 ? void 0 : _data$session.access_token) !== null && _data$session$access_ !== void 0 ? _data$session$access_ : _this.supabaseKey;
|
||||
}
|
||||
_initSupabaseAuthClient({ autoRefreshToken, persistSession, detectSessionInUrl, storage, userStorage, storageKey, flowType, lock, debug, throwOnError }, headers, fetch$1) {
|
||||
const authHeaders = {
|
||||
Authorization: `Bearer ${this.supabaseKey}`,
|
||||
apikey: `${this.supabaseKey}`
|
||||
};
|
||||
return new SupabaseAuthClient({
|
||||
url: this.authUrl.href,
|
||||
headers: _objectSpread2(_objectSpread2({}, authHeaders), headers),
|
||||
storageKey,
|
||||
autoRefreshToken,
|
||||
persistSession,
|
||||
detectSessionInUrl,
|
||||
storage,
|
||||
userStorage,
|
||||
flowType,
|
||||
lock,
|
||||
debug,
|
||||
throwOnError,
|
||||
fetch: fetch$1,
|
||||
hasCustomAuthorizationHeader: Object.keys(this.headers).some((key) => key.toLowerCase() === "authorization")
|
||||
});
|
||||
}
|
||||
_initRealtimeClient(options) {
|
||||
return new RealtimeClient(this.realtimeUrl.href, _objectSpread2(_objectSpread2({}, options), {}, { params: _objectSpread2(_objectSpread2({}, { apikey: this.supabaseKey }), options === null || options === void 0 ? void 0 : options.params) }));
|
||||
}
|
||||
_listenForAuthEvents() {
|
||||
return this.auth.onAuthStateChange((event, session) => {
|
||||
this._handleTokenChanged(event, "CLIENT", session === null || session === void 0 ? void 0 : session.access_token);
|
||||
});
|
||||
}
|
||||
_handleTokenChanged(event, source, token) {
|
||||
if ((event === "TOKEN_REFRESHED" || event === "SIGNED_IN") && this.changedAccessToken !== token) {
|
||||
this.changedAccessToken = token;
|
||||
this.realtime.setAuth(token);
|
||||
} else if (event === "SIGNED_OUT") {
|
||||
this.realtime.setAuth();
|
||||
if (source == "STORAGE") this.auth.signOut();
|
||||
this.changedAccessToken = void 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//#endregion
|
||||
//#region src/index.ts
|
||||
/**
|
||||
* Creates a new Supabase Client.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { createClient } from '@supabase/supabase-js'
|
||||
*
|
||||
* const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key')
|
||||
* const { data, error } = await supabase.from('profiles').select('*')
|
||||
* ```
|
||||
*/
|
||||
const createClient = (supabaseUrl, supabaseKey, options) => {
|
||||
return new SupabaseClient(supabaseUrl, supabaseKey, options);
|
||||
};
|
||||
function shouldShowDeprecationWarning() {
|
||||
if (typeof window !== "undefined") return false;
|
||||
const _process = globalThis["process"];
|
||||
if (!_process) return false;
|
||||
const processVersion = _process["version"];
|
||||
if (processVersion === void 0 || processVersion === null) return false;
|
||||
const versionMatch = processVersion.match(/^v(\d+)\./);
|
||||
if (!versionMatch) return false;
|
||||
return parseInt(versionMatch[1], 10) <= 18;
|
||||
}
|
||||
if (shouldShowDeprecationWarning()) console.warn("⚠️ Node.js 18 and below are deprecated and will no longer be supported in future versions of @supabase/supabase-js. Please upgrade to Node.js 20 or later. For more information, visit: https://github.com/orgs/supabase/discussions/37217");
|
||||
|
||||
//#endregion
|
||||
export { FunctionRegion, FunctionsError, FunctionsFetchError, FunctionsHttpError, FunctionsRelayError, PostgrestError, SupabaseClient, createClient };
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"name": "@supabase/supabase-js",
|
||||
"version": "2.93.3",
|
||||
"description": "Isomorphic Javascript SDK for Supabase",
|
||||
"keywords": [
|
||||
"javascript",
|
||||
"typescript",
|
||||
"supabase"
|
||||
],
|
||||
"homepage": "https://github.com/supabase/supabase-js/tree/master/packages/core/supabase-js",
|
||||
"bugs": "https://github.com/supabase/supabase-js/issues",
|
||||
"license": "MIT",
|
||||
"author": "Supabase",
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "dist/index.d.cts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/index.d.mts",
|
||||
"default": "./dist/index.mjs"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/index.d.cts",
|
||||
"default": "./dist/index.cjs"
|
||||
}
|
||||
},
|
||||
"./dist/*": "./dist/*",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/supabase/supabase-js.git",
|
||||
"directory": "packages/core/supabase-js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsdown",
|
||||
"build:watch": "tsdown --watch",
|
||||
"test": "npm run test:types && npm run test:run",
|
||||
"test:all": "npm run test:types && npm run test:run && npm run test:integration && npm run test:integration:browser",
|
||||
"test:run": "jest --runInBand --detectOpenHandles",
|
||||
"test:unit": "jest --runInBand --detectOpenHandles test/unit",
|
||||
"test:coverage": "jest --runInBand --coverage --testPathIgnorePatterns=\"test/integration|test/deno\"",
|
||||
"test:integration": "jest --runInBand --detectOpenHandles test/integration.test.ts",
|
||||
"test:integration:browser": "deno test --allow-all test/integration.browser.test.ts",
|
||||
"test:edge-functions": "cd test/deno && npm run test:edge-functions",
|
||||
"test:deno": "cd test/deno && npm run test",
|
||||
"test:watch": "jest --watch --verbose false --silent false",
|
||||
"test:node:playwright": "cd test/integration/node-browser && npm install && cp ../../../dist/umd/supabase.js . && npm run test",
|
||||
"test:bun": "cd test/integration/bun && bun install && bun test",
|
||||
"test:expo": "cd test/integration/expo && npm test",
|
||||
"test:next": "cd test/integration/next && npm test",
|
||||
"test:types": "tsd --files test/types/*.test-d.ts && jsr publish --dry-run --allow-dirty",
|
||||
"test:exports": "attw --pack .",
|
||||
"test:esm": "node test/module-resolution.test.mjs",
|
||||
"test:cjs": "node test/module-resolution.test.cjs",
|
||||
"test:module-resolution": "npm run test:exports && npm run test:esm && npm run test:cjs",
|
||||
"docs": "typedoc --entryPoints src/index.ts --out docs/v2",
|
||||
"docs:json": "typedoc --entryPoints src/index.ts --json docs/v2/spec.json --excludeExternals",
|
||||
"serve:coverage": "npx nx test:coverage supabase-js && npx --yes serve test/coverage",
|
||||
"update:test-deps": "npm run update:test-deps:expo && npm run update:test-deps:next && npm run update:test-deps:deno && npm run update:test-deps:bun",
|
||||
"update:test-deps:expo": "cd test/integration/expo && npm install",
|
||||
"update:test-deps:next": "cd test/integration/next && npm install",
|
||||
"update:test-deps:deno": "cd test/deno && npm install",
|
||||
"update:test-deps:bun": "cd test/integration/bun && bun install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@supabase/auth-js": "2.93.3",
|
||||
"@supabase/functions-js": "2.93.3",
|
||||
"@supabase/postgrest-js": "2.93.3",
|
||||
"@supabase/realtime-js": "2.93.3",
|
||||
"@supabase/storage-js": "2.93.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jsr": "^0.13.5",
|
||||
"puppeteer": "^24.9.0",
|
||||
"tsd": "^0.33.0"
|
||||
},
|
||||
"jsdelivr": "dist/umd/supabase.js",
|
||||
"unpkg": "dist/umd/supabase.js",
|
||||
"nx": {
|
||||
"targets": {
|
||||
"test:integration:browser": {
|
||||
"dependsOn": [
|
||||
{
|
||||
"projects": [
|
||||
"storage-js"
|
||||
],
|
||||
"target": "build"
|
||||
}
|
||||
]
|
||||
},
|
||||
"test:edge-functions": {
|
||||
"dependsOn": [
|
||||
{
|
||||
"projects": [
|
||||
"storage-js"
|
||||
],
|
||||
"target": "build"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user