Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
287 changes: 231 additions & 56 deletions lib/firefly.ts

Large diffs are not rendered by default.

70 changes: 56 additions & 14 deletions lib/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,45 @@ import {
FireFlyCreateOptions,
FireFlyGetOptions,
FireFlyError,
FireFlyReplaceOptions,
FireFlyUpdateOptions,
FireFlyDeleteOptions,
FireFlyIdempotencyError,
} from './interfaces';

function isSuccess(status: number) {
return status >= 200 && status < 300;
}

export function mapConfig(
options: FireFlyGetOptions | FireFlyCreateOptions | undefined,
options:
| FireFlyGetOptions
| FireFlyUpdateOptions
| FireFlyReplaceOptions
| FireFlyCreateOptions
| FireFlyDeleteOptions
| undefined,
params?: any,
): AxiosRequestConfig {
return {
const config: AxiosRequestConfig = {
...options?.requestConfig,
params: {
...params,
confirm: options?.confirm,
},
params,
};
if (options !== undefined) {
if ('confirm' in options) {
config.params = {
...config.params,
confirm: options.confirm,
};
}
if ('publish' in options) {
config.params = {
...config.params,
publish: options.publish,
};
}
}
return config;
}

export default class HttpBase {
Expand All @@ -35,17 +57,28 @@ export default class HttpBase {
this.options = this.setDefaults(options);
this.rootHttp = axios.create({
...options.requestConfig,
baseURL: `${options.host}/api/v1`,
baseURL: this.options.baseURL,
});
this.http = axios.create({
...options.requestConfig,
baseURL: `${options.host}/api/v1/namespaces/${this.options.namespace}`,
baseURL: this.options.namespaceBaseURL,
});
}

private setDefaults(options: FireFlyOptionsInput): FireFlyOptions {
const baseURLSet = (options.baseURL ?? '') !== '' && (options.namespaceBaseURL ?? '' !== '');
if (!baseURLSet && (options.host ?? '') === '') {
throw new Error('Invalid options. Option host, or baseURL and namespaceBaseURL must be set.');
}
if ((options.host ?? '') === '' && (options.websocket?.host ?? '') === '') {
throw new Error('Invalid options. Option host, or websocket.host must be set.');
}
return {
...options,
baseURL: baseURLSet ? options.baseURL : `${options.host}/api/v1`,
namespaceBaseURL: baseURLSet
? options.namespaceBaseURL
: `${options.host}/api/v1/namespaces/${options.namespace}`,
namespace: options.namespace ?? 'default',
websocket: {
...options.websocket,
Expand All @@ -59,8 +92,12 @@ export default class HttpBase {
protected async wrapError<T>(response: Promise<AxiosResponse<T>>) {
return response.catch((err) => {
if (axios.isAxiosError(err)) {
const errorMessage = err.response?.data?.error;
const ffError = new FireFlyError(errorMessage ?? err.message);
const errorMessage = err.response?.data?.error ?? err.message;
const errorClass =
errorMessage?.includes('FF10430') || errorMessage?.includes('FF10431')
? FireFlyIdempotencyError
: FireFlyError;
const ffError = new errorClass(errorMessage, err, err.request.path);
if (this.errorHandler !== undefined) {
this.errorHandler(ffError);
}
Expand Down Expand Up @@ -92,13 +129,18 @@ export default class HttpBase {
return response.data;
}

protected async replaceOne<T>(url: string, data: any) {
const response = await this.wrapError(this.http.put<T>(url, data));
protected async updateOne<T>(url: string, data: any, options?: FireFlyUpdateOptions) {
const response = await this.wrapError(this.http.patch<T>(url, data, mapConfig(options)));
return response.data;
}

protected async replaceOne<T>(url: string, data: any, options?: FireFlyReplaceOptions) {
const response = await this.wrapError(this.http.put<T>(url, data, mapConfig(options)));
return response.data;
}

protected async deleteOne<T>(url: string) {
await this.wrapError(this.http.delete<T>(url));
protected async deleteOne<T>(url: string, options?: FireFlyDeleteOptions) {
await this.wrapError(this.http.delete<T>(url, mapConfig(options)));
}

onError(handler: (err: FireFlyError) => void) {
Expand Down
118 changes: 104 additions & 14 deletions lib/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import * as http from 'http';
import { AxiosRequestConfig } from 'axios';
import * as WebSocket from 'ws';
import { operations } from './schema';

/**
Expand All @@ -13,23 +15,35 @@ import { operations } from './schema';

// General

export class FireFlyError extends Error {}
export class FireFlyError extends Error {
constructor(message?: string, public originalError?: Error, public path?: string) {
super(message);
}
}

export class FireFlyIdempotencyError extends FireFlyError {}

export interface FireFlyGetOptions {
confirm: undefined;
interface FireFlyBaseHttpOptions {
requestConfig?: AxiosRequestConfig;
}

export interface FireFlyCreateOptions {
export interface FireFlyGetOptions extends FireFlyBaseHttpOptions {}
export interface FireFlyUpdateOptions extends FireFlyBaseHttpOptions {}
export interface FireFlyReplaceOptions extends FireFlyBaseHttpOptions {}
export interface FireFlyDeleteOptions extends FireFlyBaseHttpOptions {}

export interface FireFlyCreateOptions extends FireFlyBaseHttpOptions {
confirm?: boolean;
requestConfig?: AxiosRequestConfig;
publish?: boolean;
}

export interface FireFlyOptionsInput {
host: string;
namespace?: string;
username?: string;
password?: string;
baseURL?: string;
namespaceBaseURL?: string;
websocket?: {
host?: string;
reconnectDelay?: number;
Expand All @@ -47,24 +61,50 @@ export interface FireFlyOptions extends FireFlyOptionsInput {
};
}

export interface FireFlyWebSocketSender {
send: (json: JSON) => void;
}

export interface FireFlyWebSocketConnectCallback {
(sender: FireFlyWebSocketSender): void | Promise<void>;
}

export interface FireFlyWebSocketOptions {
host: string;
namespace: string;
subscriptions: string[];
username?: string;
password?: string;
ephemeral?: FireFlyEphemeralSubscription;
autoack: boolean;
autoack?: boolean;
noack?: boolean;
reconnectDelay: number;
heartbeatInterval: number;
socketOptions?: WebSocket.ClientOptions | http.ClientRequestArgs;
afterConnect?: FireFlyWebSocketConnectCallback;
}

// Namespace
export type FireFlyNamespaceResponse = Required<
operations['getNamespace']['responses']['200']['content']['application/json']
>;

// Network

export type FireFlyIdentityFilter = operations['getIdentities']['parameters']['query'];
export type FireFlyOrganizationFilter = operations['getNetworkOrgs']['parameters']['query'];
export type FireFlyNodeFilter = operations['getNetworkNodes']['parameters']['query'];
export type FireFlyVerifierFilter = operations['getVerifiers']['parameters']['query'];

export type FireFlyIdentityRequest =
operations['postNewIdentity']['requestBody']['content']['application/json'];

export type FireFlyIdentityResponse = Required<
operations['getIdentityByID']['responses']['200']['content']['application/json']
>;
export type FireFlyIdentitiesResponse = Required<
operations['getIdentities']['responses']['200']['content']['application/json']
>;
export type FireFlyOrganizationResponse = Required<
operations['getNetworkOrg']['responses']['200']['content']['application/json']
>;
Expand Down Expand Up @@ -108,20 +148,21 @@ export interface FireFlyEphemeralSubscription extends FireFlySubscriptionBase {
}

export interface FireFlyEnrichedEvent extends FireFlyEventResponse {
blockchainEvent?: unknown;
contractAPI?: unknown;
contractInterface?: unknown;
blockchainEvent?: FireFlyBlockchainEventResponse;
contractAPI?: FireFlyContractAPIResponse;
contractInterface?: FireFlyContractInterfaceResponse;
datatype?: FireFlyDatatypeResponse;
identity?: unknown;
identity?: FireFlyIdentityResponse;
message?: FireFlyMessageResponse;
namespaceDetails?: unknown;
tokenApproval?: unknown;
tokenApproval?: FireFlyTokenApprovalResponse;
tokenPool?: FireFlyTokenPoolResponse;
tokenTransfer?: FireFlyTokenTransferResponse;
transaction?: FireFlyTransactionResponse;
operation?: FireFlyOperationResponse;
}

export interface FireFlyEventDelivery extends FireFlyEnrichedEvent {
export interface FireFlyEventDelivery extends Omit<FireFlyEnrichedEvent, 'type'> {
type: FireFlyEnrichedEvent['type'] | 'protocol_error';
subscription: {
id: string;
name: string;
Expand All @@ -145,11 +186,17 @@ export type FireFlyDataFilter = operations['getData']['parameters']['query'];

export type FireFlyDataRequest =
operations['postData']['requestBody']['content']['application/json'];
export type FireFlyDataBlobRequest =
operations['postData']['requestBody']['content']['multipart/form-data'];

export type FireFlyDataResponse = Required<
operations['getDataByID']['responses']['200']['content']['application/json']
>;

export const FireFlyDataBlobRequestDefaults: FireFlyDataBlobRequest = {
autometa: 'true',
};

// Messages

export type FireFlyMessageFilter = operations['getMsgs']['parameters']['query'];
Expand All @@ -166,6 +213,9 @@ export type FireFlyMessageResponse = Required<
export type FireFlyBatchResponse = Required<
operations['getBatchByID']['responses']['200']['content']['application/json']
>;
export type FireFlyGroupResponse = Required<
operations['getGroupByHash']['responses']['200']['content']['application/json']
>;

export interface FireFlyPrivateSendOptions extends FireFlyCreateOptions {
requestReply?: boolean;
Expand All @@ -184,6 +234,8 @@ export type FireFlyTokenPoolResponse = Required<

// Token Transfers

export type FireFlyTokenTransferFilter = operations['getTokenTransfers']['parameters']['query'];

export type FireFlyTokenMintRequest =
operations['postTokenMint']['requestBody']['content']['application/json'];
export type FireFlyTokenBurnRequest =
Expand All @@ -199,9 +251,23 @@ export type FireFlyTokenTransferResponse = Required<

export type FireFlyTokenBalanceFilter = operations['getTokenBalances']['parameters']['query'];

export type FireFlyTokenBalanceResponse = Required<
type BalancesList = Required<
operations['getTokenBalances']['responses']['200']['content']['application/json']
>;
const balances: BalancesList = [];
export type FireFlyTokenBalanceResponse = typeof balances[0];

// Token Approvals

export type FireFlyTokenApprovalFilter = operations['getTokenApprovals']['parameters']['query'];

export type FireFlyTokenApprovalRequest =
operations['postTokenApproval']['requestBody']['content']['application/json'];
type ApprovalsList =
operations['getTokenApprovals']['responses']['200']['content']['application/json'];

const approvals: ApprovalsList = [];
export type FireFlyTokenApprovalResponse = typeof approvals[0];

// Operations + Transactions

Expand Down Expand Up @@ -241,3 +307,27 @@ export type FireFlyContractAPIResponse = Required<
export type FireFlyContractListenerResponse = Required<
operations['getContractListenerByNameOrID']['responses']['200']['content']['application/json']
>;

export type FireFlyContractInvokeRequest =
operations['postContractInvoke']['requestBody']['content']['application/json'];
export type FireFlyContractAPIInvokeRequest =
operations['postContractAPIInvoke']['requestBody']['content']['application/json'];
export type FireFlyContractInvokeResponse = Required<
operations['postContractInvoke']['responses']['202']['content']['application/json']
>;

export type FireFlyContractQueryRequest =
operations['postContractQuery']['requestBody']['content']['application/json'];
export type FireFlyContractAPIQueryRequest =
operations['postContractAPIQuery']['requestBody']['content']['application/json'];
export type FireFlyContractQueryResponse = Required<
operations['postContractQuery']['responses']['200']['content']['application/json']
>;

// Blockchain Events

export type FireFlyBlockchainEventFilter = operations['getBlockchainEvents']['parameters']['query'];

export type FireFlyBlockchainEventResponse = Required<
operations['getBlockchainEventByID']['responses']['200']['content']['application/json']
>;
Loading