UNPKG

@jupyterlab/services

Version:

Client APIs for the Jupyter services REST APIs

245 lines (212 loc) 6.12 kB
// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { JSONExt, ReadonlyJSONObject } from '@lumino/coreutils'; import { Poll } from '@lumino/polling'; import { ISignal, Signal } from '@lumino/signaling'; import { BaseManager } from '../basemanager'; import { ServerConnection } from '../serverconnection'; import * as User from './user'; import { UserAPIClient } from './restapi'; /** * The service's ID. * Used to uniquely identify the poll, and * the item in local storage. */ const SERVICE_ID = '@jupyterlab/services:UserManager#user'; /** * The user API service manager. */ export class UserManager extends BaseManager implements User.IManager { /** * Create a new user manager. */ constructor(options: UserManager.IOptions = {}) { super(options); this._userApiClient = options.userApiClient ?? new UserAPIClient({ serverSettings: this.serverSettings }); // Initialize internal data. this._ready = this.requestUser() .then(() => { if (this.isDisposed) { return; } this._isReady = true; }) .catch( _ => // Return a promise that will never resolve, so user service is never ready // This typically occurs when the backend has no user service new Promise(() => { // no-op }) ); this._pollUser = new Poll({ auto: false, factory: () => this.requestUser(), frequency: { interval: 61 * 1000, backoff: true, max: 300 * 1000 }, name: SERVICE_ID, standby: options.standby ?? 'when-hidden' }); void this.ready.then(() => { void this._pollUser.start(); }); } /** * The server settings for the manager. */ readonly serverSettings: ServerConnection.ISettings; /** * Test whether the manager is ready. */ get isReady(): boolean { return this._isReady; } /** * A promise that fulfills when the manager is ready. */ get ready(): Promise<void> { return this._ready; } /** * Get the most recently fetched identity. */ get identity(): User.IIdentity | null { return this._identity; } /** * Get the most recently fetched permissions. */ get permissions(): ReadonlyJSONObject | null { return this._permissions; } /** * A signal emitted when the user changes. */ get userChanged(): ISignal<this, User.IUser> { return this._userChanged; } /** * A signal emitted when there is a connection failure. */ get connectionFailure(): ISignal<this, Error> { return this._connectionFailure; } /** * Dispose of the resources used by the manager. */ dispose(): void { this._pollUser.dispose(); super.dispose(); } /** * Force a refresh of the user data from the server. * * @returns A promise that resolves when the user info is fetched. * * #### Notes * This is intended to be called only in response to a user action, * since the manager maintains its internal state. */ async refreshUser(): Promise<void> { await this._pollUser.refresh(); await this._pollUser.tick; } /** * Execute a request to the server to poll the user and update state. */ protected async requestUser(): Promise<void> { if (this.isDisposed) { return; } const oldUser: User.IUser = { identity: this._identity, permissions: this._permissions }; const newUser = await this._userApiClient.get(); const identity = newUser.identity as Private.Writable<User.IIdentity>; // store the color and initials for the user // this info is not provided by the server const { localStorage } = window; const data = localStorage.getItem(SERVICE_ID); if (data && (!identity.initials || !identity.color)) { const localUser = JSON.parse(data); identity.initials = identity.initials || localUser.initials || identity.name.substring(0, 1); identity.color = identity.color || localUser.color || Private.getRandomColor(); } if ( !JSONExt.deepEqual( newUser as Private.WithStringIndex<User.IUser>, oldUser as Private.WithStringIndex<User.IUser> ) ) { this._identity = identity; this._permissions = newUser.permissions; localStorage.setItem(SERVICE_ID, JSON.stringify(identity)); this._userChanged.emit(newUser); } } private _isReady = false; private _ready: Promise<void>; private _pollUser: Poll; private _identity: User.IIdentity; private _permissions: ReadonlyJSONObject; private _userChanged = new Signal<this, User.IUser>(this); private _connectionFailure = new Signal<this, Error>(this); private _userApiClient: User.IUserAPIClient; } /** * A namespace for `UserManager` statics. */ export namespace UserManager { /** * The instantiation options for a user manager. */ export interface IOptions extends BaseManager.IOptions { /** * When the manager stops polling the API. Defaults to `when-hidden`. */ standby?: Poll.Standby | (() => boolean | Poll.Standby); /** * The user API client. */ userApiClient?: User.IUserAPIClient; } } /** * A namespace for module-private functionality. * * Note: We do not want to export this function * to move it to css variables in the Theme. */ namespace Private { /** * Predefined colors for users */ const userColors = [ 'var(--jp-collaborator-color1)', 'var(--jp-collaborator-color2)', 'var(--jp-collaborator-color3)', 'var(--jp-collaborator-color4)', 'var(--jp-collaborator-color5)', 'var(--jp-collaborator-color6)', 'var(--jp-collaborator-color7)' ]; /** * Get a random color from the list of colors. */ export const getRandomColor = (): string => userColors[Math.floor(Math.random() * userColors.length)]; export type Writable<T> = { -readonly [K in keyof T]: T[K]; }; export type WithStringIndex<T> = T & { [key: string]: T[keyof T] }; }