Skip to content

Commit

Permalink
add basic Api model
Browse files Browse the repository at this point in the history
  • Loading branch information
Ross Bulat committed Mar 5, 2024
1 parent d986aff commit 3bacf8e
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 0 deletions.
141 changes: 141 additions & 0 deletions src/model/Api/indexi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Copyright 2024 @rossbulat/console authors & contributors
// SPDX-License-Identifier: GPL-3.0-only

import { ApiPromise } from '@polkadot/api';
import type { VoidFn } from '@polkadot/api/types';
import { WsProvider } from '@polkadot/rpc-provider';
import type { ChainId } from 'config/networks';
import type { EventDetail, EventStatus } from './types';

export class Api {
// ------------------------------------------------------
// Class members.
// ------------------------------------------------------

// The supplied chain id.
#chainId: ChainId;

// API provider.
#provider: WsProvider;

// API provider unsubs.
#providerUnsubs: VoidFn[] = [];

// API instance.
#api: ApiPromise;

// The current RPC endpoint.
#rpcEndpoint: string;

// ------------------------------------------------------
// Getters.
// ------------------------------------------------------

get chainId() {
return this.#chainId;
}

get provider() {
return this.#provider;
}

get api() {
return this.#api;
}

get rpcEndpoint() {
return this.#rpcEndpoint;
}

// ------------------------------------------------------
// Constructor
// ------------------------------------------------------

constructor(chainId: ChainId, endpoint: string) {
this.#rpcEndpoint = endpoint;
this.#chainId = chainId;
this.initialize();
}

// ------------------------------------------------------
// Initialization
// ------------------------------------------------------

// Initialize the API.
async initialize() {
// Initialize provider.
this.#provider = new WsProvider(this.#rpcEndpoint);

// Tell UI api is connecting.
this.dispatchEvent(this.ensureEventStatus('connecting'));

// Tell UI api is connecting.
this.dispatchEvent(this.ensureEventStatus('connecting'));

// Initialise provider events.
this.initProviderEvents();

// Initialise api.
this.#api = await ApiPromise.create({ provider: this.provider });

// Tell UI api is ready.
this.dispatchEvent(this.ensureEventStatus('ready'));
}

// ------------------------------------------------------
// Event handling.
// ------------------------------------------------------

// Set up API event listeners. Relays information to `document` for the UI to handle.
async initProviderEvents() {
this.#providerUnsubs.push(
this.#provider.on('connected', () => {
this.dispatchEvent(this.ensureEventStatus('connected'));
})
);

this.#providerUnsubs.push(
this.#provider.on('disconnected', () => {
this.dispatchEvent(this.ensureEventStatus('disconnected'));
})
);
this.#providerUnsubs.push(
this.#provider.on('error', (err: string) => {
this.dispatchEvent(this.ensureEventStatus('error'), { err });
})
);
}

// Handler for dispatching events.
dispatchEvent(
event: EventStatus,
options?: {
err?: string;
}
) {
const detail: EventDetail = { event };
if (options?.err) {
detail['err'] = options.err;
}
document.dispatchEvent(new CustomEvent('api-status', { detail }));
}

// ------------------------------------------------------
// Class helpers.
// ------------------------------------------------------

// Ensures the provided status is a valid `EventStatus` being passed, or falls back to `error`.
ensureEventStatus = (status: string | EventStatus): EventStatus => {
const eventStatus: string[] = [
'connecting',
'connected',
'disconnected',
'ready',
'error',
];
if (eventStatus.includes(status)) {
return status as EventStatus;
}
return 'error' as EventStatus;
};
}
11 changes: 11 additions & 0 deletions src/model/Api/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright 2024 @rossbulat/console authors & contributors
// SPDX-License-Identifier: GPL-3.0-only

export type ApiStatus = 'connecting' | 'connected' | 'disconnected' | 'ready';

export type EventStatus = ApiStatus | 'error';

export interface EventDetail {
event: EventStatus;
err?: string;
}
7 changes: 7 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import type { EventDetail } from 'model/Api/types';
import type { CSSProperties, ReactNode } from 'react';

declare global {
interface DocumentEventMap {
'api-status': CustomEvent<EventDetail>;
}
}

export interface ComponentBase {
children?: ReactNode;
style?: CSSProperties;
Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"strictPropertyInitialization": false,
"noUnusedLocals": true,
},
"include": [
Expand Down
3 changes: 3 additions & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright 2024 @rossbulat/console authors & contributors
// SPDX-License-Identifier: GPL-3.0-only

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import checker from 'vite-plugin-checker';
Expand Down

0 comments on commit 3bacf8e

Please sign in to comment.