UNPKG

@stadium-ws/core

Version:

Build scalable realtime applications.

301 lines (245 loc) 6.63 kB
/** * TODO: * - Add socket authentication (which is required!) * - createUser * - addUserToChannel * - updateChannel * - onEvent? * - onChannelEvent? * - getChannelMessages * - getChannel * - createChannelEvent * - onNotification? * - registerUserDevice? for push notifications */ import EventEmitter from 'eventemitter3' import type { SocketMessage } from './Connection' import Connection from './Connection' import Requester from './Requester' import type { Channel, ReplyGetChannels, User, ReplyGetChannelUsers, Event, CreateEvent, QueryGetChannelEvents, ReplyGetChannelEvents, UpdateUser, ReplyGetUserRoles, ReplyGetUsers, QueryGetUserRoles, QueryGetUsers, QueryGetChannels } from './types' import type { EventName } from './utils' import { getEventName } from './utils' interface IStadiumConfig { clientId?: string clientSecret?: string apiUrl?: string gatewayUrl?: string } export class Stadium { private apiUrl: string private gatewayUrl: string private readonly config: IStadiumConfig private requester: Requester private accessToken?: string private connection?: Connection private emitter?: EventEmitter constructor (config: IStadiumConfig = {}) { this.apiUrl = config.apiUrl || 'https://api.stadium.ws' this.gatewayUrl = config.gatewayUrl || 'wss://gateway.stadium.ws' this.config = config this.requester = new Requester(this.apiUrl) } public on (event: EventName, listener: (...args: any[]) => void) { if (!this.emitter) { this.emitter = new EventEmitter() } this.emitter.on(event, listener) } public once (event: EventName, listener: (...args: any[]) => void) { if (!this.emitter) { this.emitter = new EventEmitter() } this.emitter.once(event, listener) } public off (event: EventName, listener: (...args: any[]) => void) { if (!this.emitter) { return } this.emitter.off(event, listener) } public removeAllListeners () { if (!this.emitter) { return } this.emitter.removeAllListeners() } public async createUser ({ userRoleId, displayName, meta }: { userRoleId?: string displayName?: string, meta?: any }): Promise<User> { await this.ensureAccessToken() return this.requester.request({ urlSegment: 'users', method: 'POST', body: { userRoleId, displayName, meta } }) } public setUserToken (token: string) { this.accessToken = token this.requester.setTokenHeader(this.accessToken) } public async getMe (): Promise<User> { const res = await this.requester.request<{ user: User }>({ urlSegment: 'oauth/token' }) return res.user } public async getChannel (channelId: string): Promise<Channel> { await this.ensureAccessToken() return this.requester.request({ urlSegment: `channels/${channelId}` }) } public async getChannelUsers (channelId: string): Promise<ReplyGetChannelUsers> { await this.ensureAccessToken() return this.requester.request({ urlSegment: `channels/${channelId}/users` }) } public async createEvent (options: CreateEvent): Promise<Event> { await this.ensureAccessToken() return this.requester.request({ urlSegment: 'events', method: 'POST', body: options }) } public async getChannelEvents (channelId: string, options: QueryGetChannelEvents): Promise<ReplyGetChannelEvents> { await this.ensureAccessToken() const query: QueryGetChannelEvents = { from: options?.from, limit: options?.limit, direction: options?.direction, type: options?.type } return this.requester.request({ urlSegment: `channels/${channelId}/events`, query }) } public async getUserRoles (options: QueryGetUserRoles): Promise<ReplyGetUserRoles> { await this.ensureAccessToken() const query: QueryGetUserRoles = { from: options?.from, limit: options?.limit, direction: options?.direction } return this.requester.request({ urlSegment: 'user-roles', query }) } public async getUsers (options: QueryGetUsers): Promise<ReplyGetUsers> { await this.ensureAccessToken() const query: QueryGetUserRoles = { from: options?.from, limit: options?.limit, direction: options?.direction } return this.requester.request({ urlSegment: 'users', query }) } public async getChannels (options: QueryGetChannels): Promise<ReplyGetChannels> { await this.ensureAccessToken() const query: QueryGetChannels = { from: options?.from, limit: options?.limit, direction: options?.direction } return this.requester.request({ urlSegment: 'channels', query }) } private async ensureAccessToken () { if (this.accessToken) { return } const accessTokenResponse = await this.requester.request<{ access_token: string }>({ urlSegment: 'oauth/token', method: 'POST', body: { grant_type: 'client_credentials', client_id: this.config.clientId, client_secret: this.config.clientSecret } }) if (!accessTokenResponse?.access_token) { throw new Error('Could not authenticate with Stadium') } this.accessToken = accessTokenResponse.access_token this.requester.setTokenHeader(this.accessToken) } public updateUser = async (userId: string, options: UpdateUser): Promise<User> => { await this.ensureAccessToken() const body: any = {} if (options.displayName) { body.displayName = options.displayName } if (options.isOnline) { body.isOnline = options.isOnline } if (options.meta) { body.meta = options.meta } if (options.userRoleId) { body.userRoleId = options.userRoleId } return this.requester.request({ urlSegment: `users/${userId}`, method: 'PUT', body }) } private onEvent = (event: SocketMessage) => { const eventName = getEventName(event.type) this.emitter?.emit(eventName, event.data) } public async connect () { this.connection = new Connection({ baseUrl: this.gatewayUrl }) this.emitter = new EventEmitter() await this.connection.connect({ token: this.accessToken!, onEvent: this.onEvent }) } public async disconnect () { if (!this.connection) { return } this.emitter?.removeAllListeners() return this.connection.disconnect() } }