ported all source to typescript

This commit is contained in:
Ewout Stortenbeker 2022-10-17 15:58:09 +02:00
parent 812684fdea
commit 862d8a80af
16 changed files with 1842 additions and 1519 deletions

View file

@ -1,88 +1,190 @@
const { AceBaseBase, DebugLogger, ColorStyle, DataSnapshot } = require('acebase-core');
const { WebApi } = require('./api-web');
const { AceBaseClientAuth } = require('./auth');
const { setServerBias } = require('./server-date');
import { AceBaseBase, LoggingLevel, DebugLogger, ColorStyle, DataSnapshot } from 'acebase-core';
import { WebApi } from './api-web';
import { AceBaseClientAuth } from './auth';
import { setServerBias } from './server-date';
class AceBaseClientConnectionSettings {
/**
* Settings to connect to a remote AceBase server
*/
export class AceBaseClientConnectionSettings {
/**
* Settings to connect to a remote AceBase server
* @param {object} settings
* @param {string} settings.dbname Name of the database you want to access
* @param {string} settings.host Host name, eg "localhost", or "mydb.domain.com"
* @param {number} settings.port Port number the server is running on
* @param {boolean} [settings.https=true] Use SSL (https) to access the server or not. Default: true
* @param {boolean} [settings.autoConnect=true] Automatically connect to the server, or wait until .connect is called
* @param {number} [settings.autoConnectDelay=0] Delay before auto connection. Useful for testing scenarios where both server and client start at the same time, and server needs to come online first.
* @param {object} [settings.cache] Settings for local cache
* @param {AceBase} [settings.cache.db] AceBase database instance to use for local cache
* @param {'verbose'|'log'|'warn'|'error'} [settings.logLevel='log'] debug logging level
* @param {object} [settings.sync] Settings for synchronization
* @param {'connect'|'signin'|'auto'|'manual'} [settings.sync.timing] Determines when synchronization should execute
* @param {boolean} [settings.sync.useCursor] Whether to enable cursor synchronization if transaction logging is enabled in the server configuration
* @param {object} [settings.network] Network settings
* @param {boolean} [settings.network.monitor=false] Whether to actively monitor the network for availability by pinging the server every `interval` seconds. This results in quicker offline detection. Default is `false` if `realtime` is `true` and vice versa
* @param {number} [settings.network.interval=60] Interval in seconds to send pings if `monitor` is `true`. Default is `60`
* @param {string[]} [settings.network.transports] Transport methods to try connecting to the server for realtime event notifications (in specified order). Default is `['websocket']`. Supported transport methods are `"websocket"` and `"polling"`.
* @param {boolean} [settings.network.realtime=true] Whether to connect to a serverwebsocket to enable realtime event notifications. Default is `true`. Disable this option if you only want to use the server's REST API.
* @param {boolean} [settings.sponsor=false] You can turn this on if you are a sponsor. See https://github.com/appy-one/acebase/discussions/100 for more info
* Name of the database you want to access
*/
constructor(settings) {
dbname: string;
/**
* Host name, eg "localhost", or "mydb.domain.com"
*/
host: string;
/**
* Port number the server is running on
*/
port: number;
/**
* Use SSL (https) to access the server or not. Default: `true`
* @default true
*/
https = true;
/**
* Automatically connect to the server, or wait until `db.connect()` is called
* @default true
*/
autoConnect = true;
/**
* Delay in ms before auto connecting. Useful for testing scenarios where both server and client start at the same time, and server needs to come online first.
* @default 0
*/
autoConnectDelay = 0;
/**
* Settings for local cache
*/
cache: {
/**
* AceBase database instance to use for local cache
*/
db: AceBaseBase | null;
enabled: boolean;
priority: 'cache' | 'server';
};
/**
* debug logging level
*/
logLevel: LoggingLevel = 'log';
/**
* Settings for synchronization
*/
sync: {
/**
* Determines when synchronization should execute
* @default 'auto'
*/
timing: 'connect' | 'signin' | 'auto' | 'manual';
/**
* Whether to enable cursor synchronization if transaction logging is enabled in the server configuration
* @default true
*/
useCursor: boolean;
};
/**
* Network settings
*/
network: {
/**
* Whether to actively monitor the network for availability by pinging the server every `interval` seconds.
* This results in quicker offline detection. Default is `false` if `realtime` is `true` and vice versa
*/
monitor: boolean;
/**
* Interval in seconds to send pings if `monitor` is `true`. Default is `60`
* @default 60
*/
interval: number;
/**
* Transport methods to try connecting to the server for realtime event notifications (in specified order).
* Default is `['websocket']`. Supported transport methods are `"websocket"` and `"polling"`.
* @default ['websocket']
*/
transports: Array<'websocket'|'polling'>;
/**
* Whether to connect to a serverwebsocket to enable realtime event notifications. Default is `true`.
* Disable this option if you only want to use the server's REST API.
* @default true
*/
realtime: boolean;
};
/**
* You can turn this on if you are a sponsor. See https://github.com/appy-one/acebase/discussions/100 for more info
*/
sponsor: boolean;
constructor(
settings: ConnectionSettingsInit,
) {
this.dbname = settings.dbname;
this.host = settings.host;
this.port = settings.port;
this.https = typeof settings.https === 'boolean' ? settings.https : true;
this.autoConnect = typeof settings.autoConnect === 'boolean' ? settings.autoConnect : true;
this.autoConnectDelay = typeof settings.autoConnectDelay === 'number' ? settings.autoConnectDelay : 0;
this.cache = typeof settings.cache === 'object' && typeof settings.cache.db === 'object' ? settings.cache : null; // && settings.cache.db.constructor.name.startsWith('AceBase')
this.logLevel = typeof settings.logLevel === 'string' ? settings.logLevel : 'log';
this.sponsor = typeof settings.sponsor === 'boolean' ? settings.sponsor : false;
// Set cache settings
this.cache = {
enabled: typeof settings.cache?.db === 'object' && typeof settings.cache?.enabled === 'boolean' ? settings.cache.enabled : false,
db: typeof settings.cache?.db === 'object' ? settings.cache.db : null,
priority: typeof settings.cache?.priority === 'string' && ['server','cache'].includes(settings.cache.priority) ? settings.cache.priority : 'server',
};
// Set sync settings
this.sync = settings.sync;
if (this.sync === null || typeof this.sync !== 'object') { this.sync = {}; }
if (!['connect','signin','auto','manual'].includes(this.sync.timing)) { this.sync.timing = 'auto'; }
if (typeof this.sync.useCursor !== 'boolean') { this.sync.useCursor = true; }
this.sync = {
timing: typeof settings.sync?.timing === 'string' && ['connect','signin','auto','manual'].includes(settings.sync.timing) ? settings.sync.timing : 'auto',
useCursor: typeof settings.sync?.useCursor === 'boolean' ? settings.sync.useCursor : true,
};
// Set network settings
this.network = settings.network;
if (this.network === null || typeof this.network !== 'object') { this.network = {}; }
if (!(this.network.transports instanceof Array)) { this.network.transports = ['websocket'] }
if (typeof this.network.realtime !== 'boolean') { this.network.realtime = true; }
if (typeof this.network.monitor !== 'boolean') { this.network.monitor = !this.network.realtime; }
if (typeof this.network.interval !== 'number') { this.network.interval = 60; }
const realtime = typeof settings.network?.realtime === 'boolean' ? settings.network.realtime : true;
this.network = {
transports: settings.network?.transports instanceof Array ? settings.network.transports : ['websocket'],
realtime,
monitor: typeof settings.network?.monitor === 'boolean' ? settings.network.monitor : !realtime,
interval: typeof settings.network?.interval === 'number' ? settings.network.interval : 60,
};
}
}
export type ConnectionSettingsInit = Omit<Partial<AceBaseClientConnectionSettings>, 'dbname' | 'host' | 'port' | 'sync' | 'network' | 'cache'>
& Pick<AceBaseClientConnectionSettings, 'dbname' | 'host' | 'port'>
& {
sync?: Partial<AceBaseClientConnectionSettings['sync']>;
network?: Partial<AceBaseClientConnectionSettings['network']>;
cache?: Partial<AceBaseClientConnectionSettings['cache']>;
};
/**
* AceBaseClient lets you connect to a remote (or local) AceBase server over http(s)
* @extends module:acebase-core/AceBaseBase
*/
class AceBaseClient extends AceBaseBase {
export class AceBaseClient extends AceBaseBase {
/**
* @internal (for internal use)
*/
api: WebApi;
auth: AceBaseClientAuth;
/**
* Create a client to access an AceBase server
* @param {AceBaseClientConnectionSettings} settings
*/
constructor(settings) {
if (typeof settings !== 'object') {
constructor(init: ConnectionSettingsInit) {
if (typeof init !== 'object') {
// Use old constructor signature: host, port, dbname, https = true
settings = {};
settings.host = arguments[0];
settings.port = arguments[1];
settings.dbname = arguments[2];
settings.https = arguments[3];
}
if (!(settings instanceof AceBaseClientConnectionSettings)) {
settings = new AceBaseClientConnectionSettings(settings);
// eslint-disable-next-line prefer-rest-params
const [ host, port, dbname, https ] = arguments;
init = { host, port, dbname, https };
}
const settings = init instanceof AceBaseClientConnectionSettings ? init : new AceBaseClientConnectionSettings(init);
super(settings.dbname, { info: 'realtime database client', sponsor: settings.sponsor });
/*
TODO: improve init flow with await/async (requires Node 7.6+)
TODO: improve init flow with await/async (requires Node 7.6+)
*/
const cacheDb = settings.cache && settings.cache.db;
const cacheDb = settings.cache?.db;
const cacheReadyPromise = cacheDb ? cacheDb.ready() : Promise.resolve();
let ready = false;
@ -92,7 +194,7 @@ class AceBaseClient extends AceBaseBase {
const synchronizeClocks = async () => {
// Synchronize date/time
// const start = Date.now(); // performance.now();
const info = await this.api.getServerInfo()
const info = await this.api.getServerInfo();
const now = Date.now(),
// roundtrip = now - start, //performance.now() - start,
// expectedTime = now - Math.floor(roundtrip / 2),
@ -103,34 +205,39 @@ class AceBaseClient extends AceBaseBase {
this.on('connect', () => {
// Disable cache db's ipc events, we are already notified of data changes by the server (prevents double event callbacks)
if (cacheDb) { cacheDb.settings.ipcEvents = false; }
if (cacheDb && 'settings' in (cacheDb as any)) {
(cacheDb as any).settings.ipcEvents = false;
}
synchronizeClocks();
});
this.on('disconnect', () => {
// Enable cache db's ipc events, so we get event notifications of changes by other ipc peers while offline
if (cacheDb) { cacheDb.settings.ipcEvents = true; }
if (cacheDb && 'settings' in (cacheDb as any)) {
(cacheDb as any).settings.ipcEvents = true;
}
});
this.sync = () => {
return syncPendingChanges(true);
}
this.sync = async () => {
const result = await syncPendingChanges(true);
return result as Required<Awaited<ReturnType<AceBaseClient['sync']>>>;
};
let syncRunning = false, firstSync = true;
const syncPendingChanges = async (throwErrors = false) => {
if (syncRunning) {
if (syncRunning) {
// Already syncing
if (throwErrors) { throw new Error('sync already running'); }
return;
return;
}
if (!this.api.isConnected) {
// We'll retry once connected
// // Do set firstSync to false, this fixes the issue of the first sync firing after
// // an initial succesful connection, but quick disconnect (sync does not run)
// // Do set firstSync to false, this fixes the issue of the first sync firing after
// // an initial succesful connection, but quick disconnect (sync does not run)
// // and later reconnect --> Fresh data needs to be loaded
// firstSync = false;
// firstSync = false;
if (throwErrors) { throw new Error('not connected'); }
return;
return;
}
syncRunning = true;
try {
@ -141,7 +248,7 @@ class AceBaseClient extends AceBaseBase {
eventCallback: (eventName, args) => {
this.debug.log(eventName, args || '');
this.emit(eventName, args); // this.emit('cache_sync_event', { name: eventName, args });
}
},
});
}
catch(err) {
@ -155,8 +262,8 @@ class AceBaseClient extends AceBaseBase {
syncRunning = false;
firstSync = false;
}
}
let syncTimeout = 0;
};
let syncTimeout: NodeJS.Timeout;
this.on('connect', () => {
if (settings.sync.timing === 'connect' || (settings.sync.timing === 'signin' && this.auth.accessToken)) {
syncPendingChanges();
@ -180,7 +287,16 @@ class AceBaseClient extends AceBaseBase {
this.emit('ready');
};
this.api = new WebApi(settings.dbname, { network: settings.network, sync: settings.sync, logLevel: settings.logLevel, debug: this.debug, url: `http${settings.https ? 's' : ''}://${settings.host}:${settings.port}`, autoConnect: settings.autoConnect, autoConnectDelay: settings.autoConnectDelay, cache: settings.cache }, (evt, data) => {
this.api = new WebApi(settings.dbname, {
network: settings.network,
sync: settings.sync,
logLevel: settings.logLevel,
autoConnect: settings.autoConnect,
autoConnectDelay: settings.autoConnectDelay,
cache: settings.cache,
debug: this.debug,
url: `http${settings.https ? 's' : ''}://${settings.host}:${settings.port}`,
}, (evt, data) => {
if (evt === 'connect') {
this.emit('connect');
if (!ready) {
@ -192,7 +308,7 @@ class AceBaseClient extends AceBaseBase {
if (!ready && cacheDb) { // If cache db is used, we can work without connection
emitClientReady();
}
}
}
else if (evt === 'disconnect') {
this.emit('disconnect');
}
@ -202,6 +318,10 @@ class AceBaseClient extends AceBaseBase {
});
}
async sync(): ReturnType<WebApi['sync']> {
throw new Error('Must be set by constructor');
}
get connected() {
return this.api.isConnected;
}
@ -222,7 +342,7 @@ class AceBaseClient extends AceBaseBase {
this.disconnect();
}
callExtension(method, path, data) {
callExtension(method: 'GET' | 'POST' | 'PUT' | 'DELETE', path: string, data: any) {
return this.api.callExtension(method, path, data);
}
@ -230,51 +350,50 @@ class AceBaseClient extends AceBaseBase {
* Gets the current sync cursor
*/
getCursor() {
return this.api._syncCursor;
return this.api.getSyncCursor();
}
/**
* Sets the sync cursor to use
* @param {string} cursor
*/
setCursor(cursor) {
this.api._syncCursor = cursor;
setCursor(cursor: string) {
this.api.setSyncCursor(cursor);
}
get cache() {
/**
* Clears the entire cache, or a specific path without raising any events
* @param {string} [path]
* @returns
*/
const clear = async (path = '') => {
await this.api.clearCache(path);
};
/**
* Updates the local cache with remote changes by retrieving all changes to `path` since given `cursor` and applying them to the local cache database.
* If the local path does not exist or no cursor is given, its entire value will be loaded from the server and stored in cache. If no cache database is used, an error will be thrown.
* @param {string} [path=''] Path to update. The root path will be used if not given, synchronizing the entire database.
* @param {string|null} [cursor] A previously achieved cursor to update with. Path's entire value will be loaded from the server if not given.
* @returns {Promise<{ path: string, used_cursor: string, new_cursor: string, loaded_value: boolean, changes: Array<{ path: string, previous: any, value: any, context: any }> }>}
* @param path Path to update. The root path will be used if not given, synchronizing the entire database.
* @param cursor A previously achieved cursor to update with. Path's entire value will be loaded from the server if not given.
*/
const update = (path, cursor) => {
const update = (path = '', cursor: string | null) => {
return this.api.updateCache(path, cursor);
};
/**
* Loads a value from cache database.If a cursor is provided, the cache will be updated with remote changes
* Loads a value from cache database.If a cursor is provided, the cache will be updated with remote changes
* first. If the value is not available in cache, it will be loaded from the server and stored in cache.
* This method is a shortcut for common cache logic provided by `db.ref(path).get` with the `cache_mode`
* This method is a shortcut for common cache logic provided by `db.ref(path).get` with the `cache_mode`
* and `cache_cursor` options.
* @param {string} path target path to load
* @param {string} [cursor] A previously acquired cursor
* @returns {Promise<DataSnapshot>} Returns a Promise that resolves with a snapshot of the value
* @param path target path to load
* @param cursor A previously acquired cursor
* @returns Returns a Promise that resolves with a snapshot of the value
*/
const get = async (path, cursor) => {
const get = async (path: string, cursor: string | null): Promise<DataSnapshot> => {
// Update the cache with provided cursor
return this.ref(path).get({ cache_mode: cursor ? 'allow' : 'force', cache_cursor: cursor });
const options = Object.freeze(cursor ? { cache_mode: 'allow', cache_cursor: cursor } : { cache_mode: 'force' });
const snap = await this.ref(path).get(options);
return snap;
};
return { clear, update, get };
}
}
module.exports = { AceBaseClient, AceBaseClientConnectionSettings };

File diff suppressed because it is too large Load diff

View file

@ -1,95 +1,87 @@
const { AceBaseUser, AceBaseSignInResult, AceBaseAuthResult } = require('./user');
// const { AceBaseClient } = require('./acebase-client');
import { AceBaseUser } from './user';
import type { AceBaseClient } from './acebase-client';
class AceBaseClientAuth {
export class AceBaseClientAuth {
public user: AceBaseUser | null = null;
public accessToken: string | null = null;
/**
*
* @param {AceBaseClient} client
*/
constructor(client, eventCallback) {
this.client = client;
this.eventCallback = eventCallback;
this.user = null;
this.accessToken = null;
}
constructor(private client: AceBaseClient, private eventCallback: (event: string, data: any) => void) {}
/**
* Sign into a user account using a username and password. Note that the server must have authentication enabled.
* @param {string} username Your database username
* @param {string} password Your password
* @returns {Promise<{ user: AceBaseUser, accessToken: string }>} returns a promise that resolves with the signed in user and access token
* @param username A database username
* @param password The password
* @returns returns a promise that resolves with the signed in user and access token
*/
async signIn(username, password) {
async signIn(username: string, password: string): Promise<{ user: AceBaseUser, accessToken: string }> {
if (!this.client.isReady) {
await this.client.ready();
}
const details = await this.client.api.signIn(username, password);
if (this.user) { this.eventCallback("signout", { source: "signin", user: this.user }); }
if (this.user) { this.eventCallback('signout', { source: 'signin', user: this.user }); }
this.accessToken = details.accessToken;
this.user = new AceBaseUser(details.user);
this.eventCallback("signin", { source: "signin", user: this.user, accessToken: this.accessToken });
this.eventCallback('signin', { source: 'signin', user: this.user, accessToken: this.accessToken });
return { user: this.user, accessToken: this.accessToken };
}
/**
* Sign into a user account using a username and password. Note that the server must have authentication enabled.
* @param {string} email Your email address
* @param {string} password Your password
* @returns {Promise<{ user: AceBaseUser, accessToken: string }>} returns a promise that resolves with the signed in user and access token
* @param email An email address
* @param password The password
* @returns returns a promise that resolves with the signed in user and access token
*/
async signInWithEmail(email, password) {
async signInWithEmail(email: string, password: string): Promise<{ user: AceBaseUser, accessToken: string }> {
if (!this.client.isReady) {
await this.client.ready();
}
const details = await this.client.api.signInWithEmail(email, password);
if (this.user) { this.eventCallback("signout", { source: "email_signin", user: this.user }); }
if (this.user) { this.eventCallback('signout', { source: 'email_signin', user: this.user }); }
this.accessToken = details.accessToken;
this.user = new AceBaseUser(details.user);
this.eventCallback("signin", { source: "email_signin", user: this.user, accessToken: this.accessToken });
return { user: this.user, accessToken: this.accessToken }; //success: true,
this.eventCallback('signin', { source: 'email_signin', user: this.user, accessToken: this.accessToken });
return { user: this.user, accessToken: this.accessToken }; //success: true,
}
/**
* Sign into an account using a previously assigned access token
* @param {string} accessToken a previously assigned access token
* @returns {Promise<{ user: AceBaseUser, accessToken: string }>} returns a promise that resolves with the signed in user and access token. If the token is not right, the thrown `error.code` will be `'not_found'` or `'invalid_token'`
* Sign into an account using a previously acquired access token
* @param accessToken a previously acquired access token
* @returns returns a promise that resolves with the signed in user and access token. If the token is not right, the thrown `error.code` will be `'not_found'` or `'invalid_token'`
*/
async signInWithToken(accessToken) {
async signInWithToken(accessToken: string): Promise<{ user: AceBaseUser, accessToken: string }> {
if (!this.client.isReady) {
await this.client.ready();
}
const details = await this.client.api.signInWithToken(accessToken);
if (this.user) { this.eventCallback("signout", { source: "token_signin", user: this.user }); }
if (this.user) { this.eventCallback('signout', { source: 'token_signin', user: this.user }); }
this.accessToken = details.accessToken;
this.user = new AceBaseUser(details.user);
this.eventCallback("signin", { source: "token_signin", user: this.user, accessToken: this.accessToken });
return { user: this.user, accessToken: this.accessToken }; // success: true,
this.eventCallback('signin', { source: 'token_signin', user: this.user, accessToken: this.accessToken });
return { user: this.user, accessToken: this.accessToken }; // success: true,
}
/**
* If the client is offline, you can specify an access token to automatically try signing in the user once a connection is made.
* If the client is offline, you can specify an access token to automatically try signing in the user once a connection is made.
* Doing this is recommended if you are subscribing to event paths that require user authentication/authorization. Subscribing to
* those server events will then be done after signing in, instead of failing after connecting anonymously.
* @param {string} accessToken A previously acquired access token
* @param accessToken A previously acquired access token
*/
setAccessToken(accessToken) {
setAccessToken(accessToken: string) {
this.client.api.setAccessToken(accessToken);
}
/**
* If the server has been configured with OAuth providers, use this to kick off the authentication flow.
* This method returs a Promise that resolves with the url you have to redirect your user to authenticate
* This method returs a Promise that resolves with the url you have to redirect your user to authenticate
* with the requested provider. After the user has authenticated, they will be redirected back to your callbackUrl.
* Your code in the callbackUrl will have to call finishOAuthProviderSignIn with the result querystring parameter
* to finish signing in.
* @param {string} providerName one of the configured providers (eg 'facebook', 'google', 'apple', 'spotify')
* @param {string} callbackUrl url on your website/app that will receive the sign in result
* @param {any} [options] optional provider specific authentication settings
* @returns {Promise<string>} returns a Promise that resolves with the url you have to redirect your user to.
* @param providerName one of the configured providers (eg 'facebook', 'google', 'apple', 'spotify')
* @param callbackUrl url on your website/app that will receive the sign in result
* @param options optional provider specific authentication settings
* @returns returns a Promise that resolves with the url you have to redirect your user to.
*/
async startAuthProviderSignIn(providerName, callbackUrl, options) {
async startAuthProviderSignIn(providerName: string, callbackUrl: string, options?: any): Promise<string> {
if (!this.client.isReady) {
await this.client.ready();
}
@ -99,29 +91,25 @@ class AceBaseClientAuth {
/**
* Use this method to finish OAuth flow from your callbackUrl.
* @param {string} callbackResult result received in your.callback/url?result
* @returns {Promise<{ user: AceBaseUser, accessToken: string, provider: { name: string, access_token: string, refresh_token: string, expires_in: number } }>}
* @param callbackResult result received in your.callback/url?result
*/
async finishAuthProviderSignIn(callbackResult) {
async finishAuthProviderSignIn(callbackResult: string): Promise<{ user: AceBaseUser, accessToken: string, provider: { name: string, access_token: string, refresh_token: string, expires_in: number } }> {
if (!this.client.isReady) {
await this.client.ready();
}
const details = await this.client.api.finishAuthProviderSignIn(callbackResult);
const isOtherUser = !this.user || this.user.uid !== details.user.uid;
isOtherUser && this.eventCallback("signout", { source: "oauth_signin", user: this.user });
isOtherUser && this.eventCallback('signout', { source: 'oauth_signin', user: this.user });
this.accessToken = details.accessToken;
this.user = new AceBaseUser(details.user);
isOtherUser && this.eventCallback("signin", { source: "oauth_signin", user: this.user, accessToken: this.accessToken });
return { user: this.user, accessToken: this.accessToken, provider: details.provider }; // success: true,
isOtherUser && this.eventCallback('signin', { source: 'oauth_signin', user: this.user, accessToken: this.accessToken });
return { user: this.user, accessToken: this.accessToken, provider: details.provider }; // success: true,
}
/**
* Refreshes an expiring access token with the refresh token returned from finishAuthProviderSignIn
* @param {string} providerName
* @param {string} refreshToken
* @returns {Promise<{ provider: IAceBaseAuthProviderTokens }}
*/
async refreshAuthProviderToken(providerName, refreshToken) {
async refreshAuthProviderToken(providerName: string, refreshToken: string) {
if (!this.client.isReady) {
await this.client.ready();
}
@ -133,9 +121,8 @@ class AceBaseClientAuth {
* Signs in with an external auth provider by redirecting the user to the provider's login page.
* After signing in, the user will be redirected to the current browser url. Execute
* getRedirectResult() when your page is loaded again to check if the user was authenticated.
* @param {string} providerName
*/
async signInWithRedirect(providerName) {
async signInWithRedirect(providerName: string) {
if (typeof window === 'undefined') {
throw new Error(`signInWithRedirect can only be used within a browser context`);
}
@ -143,8 +130,8 @@ class AceBaseClientAuth {
window.location.href = redirectUrl;
}
/**
* Checks if the user authentication with an auth provider.
/**
* Checks if the user authentication with an auth provider.
*/
async getRedirectResult() {
if (typeof window === 'undefined') {
@ -160,94 +147,121 @@ class AceBaseClientAuth {
/**
* Signs out of the current account
* @param {object|boolean} [options] options object, or boolean specifying whether to signout everywhere
* @param {boolean} [options.everywhere] whether to sign out all clients, or only this one
* @param {boolean} [options.clearCache] whether to clear the cache database (if used)
* @returns {Promise<void>} returns a promise that resolves when user was signed out successfully
* @param options options object, or boolean specifying whether to signout everywhere
* @returnsreturns a promise that resolves when user was signed out successfully
*/
async signOut(options) {
async signOut(options: boolean | {
/**
* whether to sign out all clients, or only this one
*/
everywhere?: boolean;
/**
* whether to clear the cache database (if used)
*/
clearCache?: boolean;
}): Promise<void> {
if (!this.client.isReady) {
await this.client.ready();
}
else if (!this.user) {
throw { code: 'not_signed_in', message: 'Not signed in!' };
}
if (this.client.isConnected) {
if (this.client.connected) {
await this.client.api.signOut(options);
}
this.accessToken = null;
let user = this.user;
const user = this.user;
this.user = null;
this.eventCallback("signout", { source: 'signout', user });
this.eventCallback('signout', { source: 'signout', user });
}
/**
* Changes the password of the currrently signed into account
* @param {string} oldPassword
* @param {string} newPassword
* @returns {Promise<{ accessToken: string }>} returns a promise that resolves with a new access token
* Changes the password of the currently signed into account
* @param oldPassword
* @param newPassword
* @returns returns a promise that resolves with a new access token
*/
async changePassword(oldPassword, newPassword) {
async changePassword(oldPassword: string, newPassword: string): Promise<{ accessToken: string }> {
if (!this.client.isReady) {
await this.client.ready();
}
else if (!this.user) {
if (!this.user) {
throw { code: 'not_signed_in', message: 'Not signed in!' };
}
const result = await this.client.api.changePassword(this.user.uid, oldPassword, newPassword);
this.accessToken = result.accessToken;
this.eventCallback("signin", { source: "password_change", user: this.user, accessToken: this.accessToken });
return { accessToken: result.accessToken }; //success: true,
this.eventCallback('signin', { source: 'password_change', user: this.user, accessToken: this.accessToken });
return { accessToken: result.accessToken }; //success: true,
}
/**
* Requests a password reset for the account with specified email address
* @param {string} email
* @returns {Promise<void>} returns a promise that resolves once the request has been processed
* @param email
* @returns returns a promise that resolves once the request has been processed
*/
async forgotPassword(email) {
async forgotPassword(email: string): Promise<void> {
if (!this.client.isReady) {
await this.client.ready();
}
return await this.client.api.forgotPassword(email);
await this.client.api.forgotPassword(email);
}
/**
* Requests a password to be changed using a previously acquired reset code, sent to the email address with forgotPassword
* @param {string} resetCode
* @param {string} newPassword
* @returns {Promise<void>} returns a promise that resolves once the password has been changed. The user is now able to sign in with the new password
* @param resetCode
* @param newPassword
* @returns returns a promise that resolves once the password has been changed. The user is now able to sign in with the new password
*/
async resetPassword(resetCode, newPassword) {
async resetPassword(resetCode: string, newPassword: string): Promise<void> {
if (!this.client.isReady) {
await this.client.ready();
}
return await this.client.api.resetPassword(resetCode, newPassword);
await this.client.api.resetPassword(resetCode, newPassword);
}
/**
* Verifies an e-mail address using the code sent to the email address upon signing up
* @param {string} verificationCode
* @returns {Promise<void>} returns a promise that resolves when verification was successful
* @param verificationCode
* @returns returns a promise that resolves when verification was successful
*/
async verifyEmailAddress(verificationCode) {
async verifyEmailAddress(verificationCode: string): Promise<void> {
if (!this.client.isReady) {
await this.client.ready();
}
return await this.client.api.verifyEmailAddress(verificationCode);
await this.client.api.verifyEmailAddress(verificationCode);
}
/**
* Updates one or more user account details
* @param {object} details
* @param {string} [details.username] New username
* @param {string} [details.email] New email address
* @param {string} [details.display_name] New display name
* @param {{ url: string, width: number, height: number }} [details.picture] New profile picture
* @param {object} [details.settings] selection of user settings to update
* @returns returns a promise with the updated user details
*/
async updateUserDetails(details) {
async updateUserDetails(details: {
/**
* New username
*/
username?: string;
/**
* New email address
*/
email?: string;
/**
* New display name
*/
display_name?: string;
/**
* New profile picture
*/
picture?: { url: string; width: number; height: number };
/**
* Selection of user settings to update
*/
settings?: Record<string, boolean | string | number>;
}) {
if (!this.client.isReady) {
await this.client.ready();
}
@ -258,72 +272,81 @@ class AceBaseClientAuth {
throw { code: 'invalid_details', message: 'details must be an object' };
}
const result = await this.client.api.updateUserDetails(details);
Object.keys(result.user).forEach(key => {
this.user[key] = result.user[key];
});
return { user: this.user }; // success: true
if (!this.user) {
// Signed out in the mean time
return { user: null };
}
for (const key of Object.keys(result.user)) {
this.user[key as keyof AceBaseUser] = result.user[key];
}
return { user: this.user };
}
/**
* Changes the username of the currrently signed into account
* @param {string} newUsername
* @returns {Promise<{ user: AceBaseUser }>} returns a promise that resolves with the updated user details
* @returns returns a promise that resolves with the updated user details
*/
async changeUsername(newUsername) {
return await this.updateUserDetails({ username: newUsername });
async changeUsername(newUsername: string) {
return this.updateUserDetails({ username: newUsername });
}
/**
* Changes the display name of the currrently signed into account
* @param {string} newName
* @returns {Promise<{ user: AceBaseUser }>} returns a promise that resolves with the updated user details
* @param newName
* @returns returns a promise that resolves with the updated user details
*/
async changeDisplayName(newName) {
return await this.updateUserDetails({ display_name: newName });
async changeDisplayName(newName: string) {
return this.updateUserDetails({ display_name: newName });
}
/**
* Changes the email address of the currrently signed in user
* @param {string} newEmail
* @returns {Promise<{ user: AceBaseUser }>} returns a promise that resolves with the updated user details
* @param newEmail
* @returns returns a promise that resolves with the updated user details
*/
async changeEmail(newEmail) {
return await this.updateUserDetails({ email: newEmail });
async changeEmail(newEmail: string) {
return this.updateUserDetails({ email: newEmail });
}
/**
* Changes the user's profile picture
* @param {object} newPicture
* @param {string} newPicture.url
* @param {number} newPicture.width
* @param {number} newPicture.height
* @returns {Promise<{ user: AceBaseUser }>} returns a promise that resolves with the updated user details
* @returns returns a promise that resolves with the updated user details
*/
async changePicture(newPicture) {
async changePicture(newPicture: {
url: string;
width: number;
height: number;
}) {
return await this.updateUserDetails({ picture: newPicture });
}
/**
* Updates settings of the currrently signed in user. Passed settings will be merged with the user's current settings
* @param {{ [key:string]: string|number|boolean }} settings - the settings to update
* @returns {Promise<{ user: AceBaseUser }>} returns a promise that resolves with the updated user details
* @param settings the settings to update
* @returns returns a promise that resolves with the updated user details
*/
async updateUserSettings(settings) {
async updateUserSettings(settings: Record<string, string | number | boolean>) {
return await this.updateUserDetails({ settings });
}
/**
* Creates a new user account with the given details. If successful, you will automatically be
* Creates a new user account with the given details. If successful, you will automatically be
* signed into the account. Note: the request will fail if the server has disabled this option
* @param {object} details
* @param {string} [details.username]
* @param {string} [details.email]
* @param {string} details.password
* @param {string} details.displayName
* @param {{ [key:string]: string|number|boolean }} [details.settings] optional settings
* @returns {Promise<{ user: AceBaseUser, accessToken: string }>} returns a promise that resolves with the signed in user and access token
* @returns returns a promise that resolves with the signed in user and access token
*/
async signUp(details) {
async signUp(details:
(
{ username: string; email?: string; }
| { username?: string; email: string;}
) & {
password: string;
displayName: string;
/**
* optional settings
*/
settings?: Record<string, string | number | boolean>;
},
): Promise<{ user: AceBaseUser, accessToken?: string }> {
if (!details.username && !details.email) {
throw { code: 'invalid_details', message: 'No username or email set' };
}
@ -336,9 +359,9 @@ class AceBaseClientAuth {
const isAdmin = this.user && this.user.uid === 'admin';
if (this.user && !isAdmin) {
// Sign out of current account
let user = this.user;
const user = this.user;
this.user = null;
this.eventCallback("signout", { source: 'signup', user } );
this.eventCallback('signout', { source: 'signup', user } );
}
const result = await this.client.api.signUp(details, !isAdmin);
if (isAdmin) {
@ -348,8 +371,8 @@ class AceBaseClientAuth {
// Sign into new account
this.accessToken = result.accessToken;
this.user = new AceBaseUser(result.user);
this.eventCallback("signin", { source: "signup", user: this.user, accessToken: this.accessToken });
return { user: this.user, accessToken: this.accessToken }; //success: true,
this.eventCallback('signin', { source: 'signup', user: this.user, accessToken: this.accessToken });
return { user: this.user, accessToken: this.accessToken as string }; //success: true,
}
}
@ -357,10 +380,9 @@ class AceBaseClientAuth {
* Removes the currently signed in user account and signs out. Note: this will only
* remove the database user account, not any data stored in the database by this user. It is
* your own responsibility to remove that data.
* @param {string} [uid] for admin user only: remove account with uid
* @returns {Promise<void>}
* @param uid for admin user only: remove account with specific uid
*/
async deleteAccount(uid) {
async deleteAccount(uid?: string): Promise<void> {
if (!this.client.isReady) {
await this.client.ready();
}
@ -379,11 +401,9 @@ class AceBaseClientAuth {
if (signOut) {
// Sign out of the account
this.accessToken = null;
let user = this.user;
const user = this.user;
this.user = null;
this.eventCallback("signout", { source: 'delete_account', user });
this.eventCallback('signout', { source: 'delete_account', user });
}
}
}
module.exports = { AceBaseClientAuth };

View file

@ -1,9 +1,7 @@
const Base64 = {
encode(str) {
return btoa(unescape(encodeURIComponent(str)));
},
decode(base64) {
return decodeURIComponent(escape(atob(base64)));
}
};
module.exports = Base64;
export function encode(str: string) {
return btoa(unescape(encodeURIComponent(str)));
}
export function decode(base64: string) {
return decodeURIComponent(escape(atob(base64)));
}

View file

@ -1,9 +1,7 @@
const Base64 = {
encode(str) {
return Buffer.from(str, 'utf8').toString('base64');
},
decode(base64) {
return Buffer.from(base64, 'base64').toString('utf8');
}
};
module.exports = Base64;
export function encode(str: string) {
return Buffer.from(str, 'utf8').toString('base64');
}
export function decode(base64: string) {
return Buffer.from(base64, 'base64').toString('utf8');
}

View file

@ -12,8 +12,9 @@
</script>
*/
const acebaseclient = require('./index');
import * as acebaseclient from './index';
window.acebaseclient = acebaseclient;
window.AceBaseClient = acebaseclient.AceBaseClient; // Shortcut to AceBaseClient
module.exports = acebaseclient;
(window as any).acebaseclient = acebaseclient;
(window as any).AceBaseClient = acebaseclient.AceBaseClient; // Shortcut to AceBaseClient
export * from './index';

View file

@ -1,9 +1,7 @@
class CachedValueUnavailableError extends Error {
constructor(path, message) {
export class CachedValueUnavailableError extends Error {
public serverError?: any;
constructor(public path: string, message?: string) {
super(message || `Value for path "/${path}" is not available in cache`);
this.path = path;
}
}
module.exports = { CachedValueUnavailableError };

View file

@ -1,20 +1,4 @@
const { DataReference, DataSnapshot, EventSubscription, PathReference, TypeMappings, ID, proxyAccess, ObjectCollection, PartialArray, Transport } = require('acebase-core');
const { AceBaseClient } = require('./acebase-client');
const { ServerDate } = require('./server-date');
const { CachedValueUnavailableError } = require('./errors');
module.exports = {
AceBaseClient,
DataReference,
DataSnapshot,
EventSubscription,
PathReference,
TypeMappings,
ID,
proxyAccess,
ServerDate,
ObjectCollection,
CachedValueUnavailableError,
PartialArray,
Transport
};
export { DataReference, DataSnapshot, EventSubscription, PathReference, TypeMappings, ID, proxyAccess, ObjectCollection, PartialArray, Transport } from 'acebase-core';
export { AceBaseClient } from './acebase-client';
export { ServerDate } from './server-date';
export { CachedValueUnavailableError } from './errors';

View file

@ -1 +1 @@
module.exports = performance;
export default performance;

View file

@ -1,2 +1,2 @@
const { performance } = require('perf_hooks');
module.exports = performance;
import { performance } from 'perf_hooks';
export default performance;

View file

@ -1,13 +1,11 @@
class PromiseTimeoutError extends Error {}
function promiseTimeout(promise, ms, comment) {
return new Promise((resolve, reject) => {
let timeout;
function success(result) {
export class PromiseTimeoutError extends Error {}
export function promiseTimeout<T = any>(promise: Promise<T>, ms: number, comment?: string) {
return new Promise<T>((resolve, reject) => {
const timeout: NodeJS.Timeout = setTimeout(() => reject(new PromiseTimeoutError(`Promise ${comment ? `"${comment}" ` : ''}timed out after ${ms}ms`)), ms);
function success(result: T) {
clearTimeout(timeout);
resolve(result);
}
promise.then(success).catch(reject);
timeout = setTimeout(() => reject(new PromiseTimeoutError(`Promise ${comment ? `"${comment}" ` : ''}timed out after ${ms}ms`)), ms);
});
}
module.exports = { PromiseTimeoutError, promiseTimeout };

View file

@ -1,9 +1,19 @@
const { AceBaseRequestError } = require('./error');
import { AceBaseRequestError } from './error';
/**
* @returns {Promise<{ context: any, data: any }>} returns a promise that resolves with an object containing data and an optionally returned context
* @returns returns a promise that resolves with an object containing data and an optionally returned context
*/
async function request(method, url, options = { accessToken: null, data: null, dataReceivedCallback: null, dataRequestCallback: null, context: null }) {
export default async function request(
method: 'GET'|'POST'|'PUT'|'DELETE',
url: string,
options: {
accessToken?: string | null;
data?: any;
dataReceivedCallback?: ((chunk: any) => void) | null;
dataRequestCallback?: ((bytes: number) => Promise<any> | any) | null;
context?: any;
} = { accessToken: null, data: null, dataReceivedCallback: null, dataRequestCallback: null, context: null },
): Promise<{ context: any, data: any }> {
let postData = options.data;
if (typeof postData === 'undefined' || postData === null) {
postData = '';
@ -11,27 +21,28 @@ async function request(method, url, options = { accessToken: null, data: null, d
else if (typeof postData === 'object') {
postData = JSON.stringify(postData);
}
const headers = {
'AceBase-Context': JSON.stringify(options.context || null)
const headers: Record<string, string> = {
'AceBase-Context': JSON.stringify(options.context || null),
};
const init = {
method,
headers
headers,
body: undefined as any,
};
if (typeof options.dataRequestCallback === 'function') {
// Stream data to the server instead of posting all from memory at once
headers['Content-Type'] = 'text/plain'; // Prevent server middleware parsing the content as JSON
const supportsStreaming = false;
if (supportsStreaming) {
// Streaming uploads appears not to be implemented in Chromium/Chrome yet.
// Streaming uploads appears not to be implemented in Chromium/Chrome yet.
// Setting the body property to a ReadableStream results in the string "[object ReadableStream]"
// See https://bugs.chromium.org/p/chromium/issues/detail?id=688906 and https://stackoverflow.com/questions/40939857/fetch-with-readablestream-as-request-body
let canceled = false;
init.body = new ReadableStream({
async pull(controller) {
const chunkSize = controller.desiredSize || 1024 * 16;
let chunk = await options.dataRequestCallback(chunkSize);
const chunk = await options.dataRequestCallback?.(chunkSize);
if (canceled || [null,''].includes(chunk)) {
controller.close();
}
@ -39,10 +50,11 @@ async function request(method, url, options = { accessToken: null, data: null, d
controller.enqueue(chunk);
}
},
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
async start(controller) {},
cancel() {
canceled = true;
}
},
});
}
else {
@ -63,7 +75,7 @@ async function request(method, url, options = { accessToken: null, data: null, d
if (options.accessToken) {
headers['Authorization'] = `Bearer ${options.accessToken}`;
}
const request = { url, method, headers };
const request = { url, method, headers, body: undefined as any };
const res = await fetch(request.url, init).catch(err => {
// console.error(err);
throw new AceBaseRequestError(request, null, 'fetch_failed', err.message);
@ -71,21 +83,21 @@ async function request(method, url, options = { accessToken: null, data: null, d
let data = '';
if (typeof options.dataReceivedCallback === 'function') {
// Stream response
const reader = res.body.getReader();
await new Promise((resolve, reject) => {
(function readNext() {
reader.read()
.then(result => {
options.dataReceivedCallback(result.value);
if (result.done) { return resolve(); }
const reader = res.body?.getReader();
await new Promise<void>((resolve, reject) => {
(async function readNext() {
try {
const result = await reader?.read();
options.dataReceivedCallback?.(result?.value);
if (result?.done) { return resolve(); }
readNext();
})
.catch(err => {
reader.cancel('error');
}
catch (err) {
reader?.cancel('error');
reject(err);
});
}
})();
})
});
}
else {
data = await res.text();
@ -93,8 +105,9 @@ async function request(method, url, options = { accessToken: null, data: null, d
const isJSON = data[0] === '{' || data[0] === '['; // || (res.headers['content-type'] || '').startsWith('application/json')
if (res.status === 200) {
let context = res.headers.get('AceBase-Context');
if (context && context[0] === '{') { context = JSON.parse(context); }
const contextHeader = res.headers.get('AceBase-Context');
let context: Record<string, any>;
if (contextHeader && contextHeader[0] === '{') { context = JSON.parse(contextHeader); }
else { context = {}; }
if (isJSON) { data = JSON.parse(data); }
return { context, data };
@ -105,11 +118,11 @@ async function request(method, url, options = { accessToken: null, data: null, d
statusCode: res.status,
statusMessage: res.statusText,
headers: res.headers,
body: data
body: data,
};
let code = res.status, message = res.statusText;
if (isJSON) {
let err = JSON.parse(data);
const err = JSON.parse(data);
if (err.code) { code = err.code; }
if (err.message) { message = err.message; }
}
@ -118,4 +131,3 @@ async function request(method, url, options = { accessToken: null, data: null, d
}
module.exports = request;

View file

@ -1,14 +1,9 @@
class AceBaseRequestError extends Error {
export class AceBaseRequestError extends Error {
get isNetworkError() {
return this.response === null;
}
constructor(request, response, code, message) {
constructor(public request: any, public response: any, public code?: number | string, public message: string = 'unknown error') {
super(message);
this.code = code;
this.request = request;
this.response = response;
}
}
const NOT_CONNECTED_ERROR_MESSAGE = 'remote database is not connected'; //'AceBaseClient is not connected';
module.exports = { AceBaseRequestError, NOT_CONNECTED_ERROR_MESSAGE };
export const NOT_CONNECTED_ERROR_MESSAGE = 'remote database is not connected'; //'AceBaseClient is not connected';

View file

@ -1,14 +1,24 @@
const http = require('http');
const https = require('https');
const URL = require('url').URL;
const { AceBaseRequestError } = require('./error');
import * as http from 'http';
import * as https from 'https';
import { URL } from 'url';
import { AceBaseRequestError } from './error';
/**
* @returns {Promise<{ context: any, data: any }>} returns a promise that resolves with an object containing data and an optionally returned context
* @returns returns a promise that resolves with an object containing data and an optionally returned context
*/
function request(method, url, options = { accessToken: null, data: null, dataReceivedCallback: null, dataRequestCallback: null, context: null }) {
return new Promise(async (resolve, reject) => {
let endpoint = new URL(url); // URL.parse(url);
export default function request(
method: 'GET'|'POST'|'PUT'|'DELETE',
url: string,
options: {
accessToken?: string | null;
data?: any;
dataReceivedCallback?: ((chunk: any) => void) | null;
dataRequestCallback?: ((bytes: number) => Promise<any> | any) | null;
context?: any;
} = { accessToken: null, data: null, dataReceivedCallback: null, dataRequestCallback: null, context: null },
) {
return new Promise<{ context: any, data: any }>(async (resolve, reject) => {
const endpoint = new URL(url); // URL.parse(url);
let postData = options.data;
if (typeof postData === 'undefined' || postData === null) {
@ -24,8 +34,9 @@ function request(method, url, options = { accessToken: null, data: null, dataRec
port: endpoint.port,
path: endpoint.pathname + endpoint.search, //endpoint.path,
headers: {
'AceBase-Context': JSON.stringify(options.context || null)
}
'AceBase-Context': JSON.stringify(options.context || null),
} as Record<string, string | number>,
body: undefined as any,
};
if (method !== 'GET') {
if (typeof options.dataRequestCallback !== 'function') {
@ -38,7 +49,7 @@ function request(method, url, options = { accessToken: null, data: null, dataRec
}
const client = request.protocol === 'https:' ? https : http;
const req = client.request(request, res => {
res.setEncoding("utf8");
res.setEncoding('utf8');
let data = '';
if (typeof options.dataReceivedCallback === 'function') {
res.on('data', options.dataReceivedCallback);
@ -49,8 +60,9 @@ function request(method, url, options = { accessToken: null, data: null, dataRec
res.on('end', () => {
const isJSON = data[0] === '{' || data[0] === '['; // || (res.headers['content-type'] || '').startsWith('application/json')
if (res.statusCode === 200) {
let context = res.headers['acebase-context']; // lowercase header names only
if (context && context[0] === '{') { context = JSON.parse(context); }
const contextHeader = res.headers['acebase-context'] as string; // lowercase header names only
let context: Record<string, any>;
if (contextHeader && contextHeader[0] === '{') { context = JSON.parse(contextHeader); }
else { context = {}; }
if (isJSON) { data = JSON.parse(data); }
resolve({ context, data });
@ -61,11 +73,11 @@ function request(method, url, options = { accessToken: null, data: null, dataRec
statusCode: res.statusCode,
statusMessage: res.statusMessage,
headers: res.headers,
body: data
body: data,
};
let code = res.statusCode, message = res.statusMessage;
if (isJSON) {
let err = JSON.parse(data);
const err = JSON.parse(data);
if (err.code) { code = err.code; }
if (err.message) { message = err.message; }
}
@ -75,7 +87,7 @@ function request(method, url, options = { accessToken: null, data: null, dataRec
});
req.on('error', (err) => {
reject(new AceBaseRequestError(request, null, err.code || err.name, err.message));
reject(new AceBaseRequestError(request, null, (err as any).code || err.name, err.message));
});
if (typeof options.dataRequestCallback === 'function') {
@ -83,7 +95,7 @@ function request(method, url, options = { accessToken: null, data: null, dataRec
const chunkSize = req.writableHighWaterMark || 1024 * 16;
let chunk;
while (![null,''].includes(chunk = await options.dataRequestCallback(chunkSize))) {
let ok = req.write(chunk);
const ok = req.write(chunk);
if (!ok) { await new Promise(resolve => req.once('drain', resolve)); }
}
}
@ -92,6 +104,4 @@ function request(method, url, options = { accessToken: null, data: null, dataRec
}
req.end();
});
};
module.exports = request;
}

View file

@ -1,12 +1,12 @@
const { ID } = require('acebase-core');
const performance = require('./performance');
import { ID } from 'acebase-core';
import performance from './performance';
let time = {
const time = {
serverBias: 0,
localBias: 0,
lastTime: Date.now(),
lastPerf: performance.now(),
get bias() { return this.serverBias + this.localBias; }
get bias() { return this.serverBias + this.localBias; },
};
function biasChanged() {
@ -39,7 +39,7 @@ function scheduleLocalTimeCheck() {
}
scheduleLocalTimeCheck();
function setServerBias(bias) {
export function setServerBias(bias: number) {
if (typeof bias === 'number') {
time.serverBias = bias;
time.localBias = 0;
@ -47,11 +47,9 @@ function setServerBias(bias) {
}
}
class ServerDate extends Date {
export class ServerDate extends Date {
constructor() {
const biasedTime = Date.now() + time.bias;
super(biasedTime);
}
}
module.exports = { ServerDate, setServerBias };

View file

@ -1,62 +1,85 @@
class AceBaseUser {
export class AceBaseUser {
/**
*
* @param {{ uid: string, username?: string, email?: string, displayName: string, created: Date, last_signin: Date, last_signin_ip: string settings: { [key:string]: string|number|boolean } }} user
* unique id
*/
constructor(user) {
// /** @type {string} unique id */
// this.uid = user.uid;
// /** @type {string?} username */
// this.username = user.username;
// /** @type {string?} email address */
// this.email = user.email;
// /** @type {string?} display or screen name */
// this.displayName = user.displayName;
// this.settings = user.settings;
// this.created = user.created;
// this.last_signin = user.last_signin;
// this.last_signin_ip = user.last_signin_ip;
// this.prev_signin = user.prev_signin;
// this.prev_signin_ip = user.prev_signin_ip;
uid: string;
/**
* username used for signing in
*/
username?: string;
/**
* email address used for signing in
*/
email?: string;
/**
* display or screen name
*/
displayName: string;
/**
* Date/time this user record was created
*/
created: Date;
/**
* Date/time this user previously signed in
*/
last_signin?: Date;
/**
* IP address of last signin
*/
last_signin_ip?: string;
/**
* Additional saved user settings & info
*/
settings: { [key:string]: string|number|boolean };
constructor(user: Partial<AceBaseUser>) {
Object.assign(this, user);
if (!user.uid) { throw new Error('User details is missing required uid field'); }
this.uid = user.uid;
this.displayName = user.displayName ?? 'unknown';
this.created = user.created ?? new Date(0);
this.settings = user.settings ?? {};
}
}
class AceBaseSignInResult {
/**
*
* @param {object} result
* @param {boolean} result.success
* @param {AceBaseUser} [result.user]
* @param {string} [result.accessToken]
* @param {{ code: string, message: string }} [result.reason]
*/
constructor(result) {
this.success = result.success;
if (result.success) {
this.user = result.user;
this.accessToken = result.accessToken;
}
else {
this.reason = result.reason;
}
}
}
export type AceBaseSignInSuccess = { success: true; user: AceBaseUser; accessToken: string; provider: string };
export type AceBaseSignInFailure = { success: false; reason: { code: string, message: string }; };
export type AceBaseSignInResult = AceBaseSignInSuccess | AceBaseSignInFailure;
// export class AceBaseSignInResult {
// success: boolean;
// user?: AceBaseUser;
// accessToken?: string;
// reason?: { code: string, message: string };
// constructor(result: { success: boolean; user?: AceBaseUser; accessToken?: string; reason?: { code: string, message: string } }) {
// this.success = result.success;
// if (result.success) {
// this.user = result.user;
// this.accessToken = result.accessToken;
// }
// else {
// this.reason = result.reason;
// }
// }
// }
class AceBaseAuthResult {
/**
*
* @param {object} result
* @param {boolean} result.success
* @param {{ code: string, message: string }} [result.reason]
*/
constructor(result) {
this.success = result.success;
if (!result.success) {
this.reason = result.reason;
}
}
}
export type AceBaseAuthSuccess = { success: true };
export type AceBaseAuthFailure = { success: false; reason: { code: string, message: string } };
export type AceBaseAuthResult = AceBaseAuthSuccess | AceBaseAuthFailure;
module.exports = { AceBaseUser, AceBaseSignInResult, AceBaseAuthResult };
// export class AceBaseAuthResult {
// success: boolean;
// reason?: { code: string, message: string };
// constructor(result: { success: boolean; reason?: { code: string, message: string }}) {
// this.success = result.success;
// if (!result.success) {
// this.reason = result.reason;
// }
// }
// }