draft: ton-x transport & separate from tonhub http api
parent
dc723740dc
commit
b31fa5f931
|
@ -7,6 +7,8 @@ import { delay } from "teslabot";
|
||||||
import { Cell, Address, beginCell, CommentMessage, safeSign, contractAddress, safeSignVerify } from 'ton';
|
import { Cell, Address, beginCell, CommentMessage, safeSign, contractAddress, safeSignVerify } from 'ton';
|
||||||
import BN from 'bn.js';
|
import BN from 'bn.js';
|
||||||
import { WalletV4Source } from 'ton-contracts';
|
import { WalletV4Source } from 'ton-contracts';
|
||||||
|
import { TonhubHttpTransport } from '../transport/TonhubHttpTransport';
|
||||||
|
import { TonXTransport } from '../transport/TonXTransport';
|
||||||
|
|
||||||
const sessionStateCodec = t.union([
|
const sessionStateCodec = t.union([
|
||||||
t.type({
|
t.type({
|
||||||
|
@ -166,6 +168,16 @@ function textToCell(src: string) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function autodiscoverTransport(config?: { adapter: any }) {
|
||||||
|
if (TonXTransport.isAvailable()) {
|
||||||
|
return new TonXTransport();
|
||||||
|
}
|
||||||
|
return new TonhubHttpTransport({
|
||||||
|
adapter: config?.adapter,
|
||||||
|
endpoint: 'https://connect.tonhubapi.com'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export class TonhubConnector {
|
export class TonhubConnector {
|
||||||
|
|
||||||
static extractPublicKey(config: {
|
static extractPublicKey(config: {
|
||||||
|
@ -265,9 +277,10 @@ export class TonhubConnector {
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly testnet: boolean;
|
readonly testnet: boolean;
|
||||||
readonly adapter: any | null;
|
readonly transport: Transport;
|
||||||
|
|
||||||
|
|
||||||
constructor(args?: { testnet?: boolean, adapter?: any }) {
|
constructor(args?: { testnet?: boolean, adapter?: any, transport?: Transport }) {
|
||||||
let testnet = false;
|
let testnet = false;
|
||||||
let adapter: any | null = null;
|
let adapter: any | null = null;
|
||||||
if (args) {
|
if (args) {
|
||||||
|
@ -279,7 +292,7 @@ export class TonhubConnector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.testnet = testnet;
|
this.testnet = testnet;
|
||||||
this.adapter = adapter;
|
this.transport = args?.transport || autodiscoverTransport({ adapter });
|
||||||
}
|
}
|
||||||
|
|
||||||
createNewSession = async (args: { name: string, url: string }): Promise<TonhubCreatedSession> => {
|
createNewSession = async (args: { name: string, url: string }): Promise<TonhubCreatedSession> => {
|
||||||
|
@ -291,12 +304,14 @@ export class TonhubConnector {
|
||||||
|
|
||||||
// Request new session
|
// Request new session
|
||||||
await backoff(async () => {
|
await backoff(async () => {
|
||||||
let session = await axios.post('https://connect.tonhubapi.com/connect/init', {
|
let session = await this.transport.call('session_new', {
|
||||||
key: sessionId,
|
key: sessionId,
|
||||||
testnet: this.testnet,
|
testnet: this.testnet,
|
||||||
name: args.name,
|
name: args.name,
|
||||||
url: args.url
|
url: args.url,
|
||||||
}, this.adapter ? { timeout: 5000, adapter: this.adapter } : { timeout: 5000 });
|
});
|
||||||
|
|
||||||
|
|
||||||
if (!session.data.ok) {
|
if (!session.data.ok) {
|
||||||
throw Error('Unable to create state');
|
throw Error('Unable to create state');
|
||||||
}
|
}
|
||||||
|
@ -359,24 +374,23 @@ export class TonhubConnector {
|
||||||
|
|
||||||
getSessionState = async (sessionId: string): Promise<TonhubSessionState> => {
|
getSessionState = async (sessionId: string): Promise<TonhubSessionState> => {
|
||||||
return await backoff(async () => {
|
return await backoff(async () => {
|
||||||
let ex = (await axios.get('https://connect.tonhubapi.com/connect/' + sessionId, this.adapter ? { timeout: 5000, adapter: this.adapter } : { timeout: 5000 })).data;
|
let session = this.transport.call('session_get', {
|
||||||
return this.ensureSessionStateCorrect(sessionId, ex);
|
id: sessionId
|
||||||
|
});
|
||||||
|
return this.ensureSessionStateCorrect(sessionId, session);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForSessionState = async (sessionId: string, lastUpdated?: number): Promise<TonhubSessionState> => {
|
waitForSessionState = async (sessionId: string, lastUpdated?: number): Promise<TonhubSessionState> => {
|
||||||
return await backoff(async () => {
|
return await backoff(async () => {
|
||||||
let params: AxiosRequestConfig = this.adapter ? { timeout: 30000, adapter: this.adapter } : { timeout: 30000 }
|
let session = this.transport.call('session_wait', {
|
||||||
params.timeout = 30000
|
id: sessionId
|
||||||
params.params = {
|
});
|
||||||
lastUpdated
|
return this.ensureSessionStateCorrect(sessionId, session);
|
||||||
};
|
|
||||||
let ex = (await axios.get('https://connect.tonhubapi.com/connect/' + sessionId + '/wait', params)).data;
|
|
||||||
return this.ensureSessionStateCorrect(sessionId, ex);
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
awaitSessionReady = async (sessionId: string, timeout: number, lastUpdated: number): Promise<TonhubSessionAwaited> => {
|
awaitSessionReady = async (sessionId: string, timeout: number): Promise<TonhubSessionAwaited> => {
|
||||||
let expires = Date.now() + timeout;
|
let expires = Date.now() + timeout;
|
||||||
let res: TonhubSessionStateReady | TonhubSessionStateExpired | TonhubSessionStateRevoked = await backoff(async () => {
|
let res: TonhubSessionStateReady | TonhubSessionStateExpired | TonhubSessionStateRevoked = await backoff(async () => {
|
||||||
while (Date.now() < expires) {
|
while (Date.now() < expires) {
|
||||||
|
@ -459,7 +473,9 @@ export class TonhubConnector {
|
||||||
let boc = pkg.toBoc({ idx: false }).toString('base64');
|
let boc = pkg.toBoc({ idx: false }).toString('base64');
|
||||||
|
|
||||||
// Post command
|
// Post command
|
||||||
await backoff(() => axios.post('https://connect.tonhubapi.com/connect/command', { job: boc }, this.adapter ? { timeout: 5000, adapter: this.adapter } : { timeout: 5000 }));
|
await backoff(() => this.transport.call('command_new', {
|
||||||
|
job: boc,
|
||||||
|
}));
|
||||||
|
|
||||||
// Await result
|
// Await result
|
||||||
let result = await this._awaitJobState(request.appPublicKey, boc);
|
let result = await this._awaitJobState(request.appPublicKey, boc);
|
||||||
|
@ -522,8 +538,11 @@ export class TonhubConnector {
|
||||||
.endCell();
|
.endCell();
|
||||||
let boc = pkg.toBoc({ idx: false }).toString('base64');
|
let boc = pkg.toBoc({ idx: false }).toString('base64');
|
||||||
|
|
||||||
|
|
||||||
// Post command
|
// Post command
|
||||||
await backoff(() => axios.post('https://connect.tonhubapi.com/connect/command', { job: boc }, this.adapter ? { timeout: 5000, adapter: this.adapter } : { timeout: 5000 }));
|
await backoff(() => this.transport.call('command_new', {
|
||||||
|
job: boc,
|
||||||
|
}));
|
||||||
|
|
||||||
// Await result
|
// Await result
|
||||||
let result = await this._awaitJobState(request.appPublicKey, boc);
|
let result = await this._awaitJobState(request.appPublicKey, boc);
|
||||||
|
@ -563,7 +582,9 @@ export class TonhubConnector {
|
||||||
|
|
||||||
private _getJobState = async (appPublicKey: string, boc: string): Promise<{ type: 'expired' | 'rejected' | 'submitted' } | { type: 'completed', result: string }> => {
|
private _getJobState = async (appPublicKey: string, boc: string): Promise<{ type: 'expired' | 'rejected' | 'submitted' } | { type: 'completed', result: string }> => {
|
||||||
let appk = toUrlSafe(appPublicKey);
|
let appk = toUrlSafe(appPublicKey);
|
||||||
let res = (await axios.get('https://connect.tonhubapi.com/connect/command/' + appk, this.adapter ? { timeout: 5000, adapter: this.adapter } : { timeout: 5000 })).data;
|
|
||||||
|
let res = await this.transport.call('command_get', { appk });
|
||||||
|
|
||||||
if (!jobStateCodec.is(res)) {
|
if (!jobStateCodec.is(res)) {
|
||||||
throw Error('Invalid response from server');
|
throw Error('Invalid response from server');
|
||||||
}
|
}
|
||||||
|
@ -571,7 +592,7 @@ export class TonhubConnector {
|
||||||
return { type: 'expired' };
|
return { type: 'expired' };
|
||||||
}
|
}
|
||||||
if (res.job !== boc) {
|
if (res.job !== boc) {
|
||||||
return { type: 'rejected' };
|
return { type: 'rejected' };
|
||||||
}
|
}
|
||||||
if (res.state === 'expired') {
|
if (res.state === 'expired') {
|
||||||
return { type: 'expired' };
|
return { type: 'expired' };
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
export class TonXTransport implements Transport {
|
||||||
|
call<TResult, TArgs>(method: string, args: TArgs): Promise<TResult> {
|
||||||
|
let tonX = TonXTransport.getTonX();
|
||||||
|
if (!tonX) {
|
||||||
|
throw new Error('ton-x not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return tonX.call(method, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getTonX() {
|
||||||
|
if (!window || !(window as any).tonX) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return window && (window as any).tonX as {
|
||||||
|
call: <TResult, TPayload>(type: string, payload: TPayload) => Promise<TResult>
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static isAvailable() {
|
||||||
|
return this.getTonX() !== null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export class TonhubHttpTransport implements Transport {
|
||||||
|
private readonly _endpoint: string;
|
||||||
|
private readonly _timeout: number;
|
||||||
|
|
||||||
|
private adapter?: any;
|
||||||
|
|
||||||
|
constructor(config?: { endpoint?: string, adapter?: any, timeout?: number }) {
|
||||||
|
this._endpoint = config?.endpoint || 'https://connect.tonhubapi.com';
|
||||||
|
this._timeout = config?.timeout || 5000;
|
||||||
|
this.adapter = config?.adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAxiosConfig() {
|
||||||
|
return this.adapter ? { timeout: this._timeout, adapter: this.adapter } : { timeout: this._timeout };
|
||||||
|
}
|
||||||
|
|
||||||
|
call(method: string, args: any): Promise<any> {
|
||||||
|
if (method === 'session_new') {
|
||||||
|
return this.createSession(args);
|
||||||
|
} else if (method === 'session_get') {
|
||||||
|
return this.getSession(args);
|
||||||
|
} else if (method === 'session_wait') {
|
||||||
|
return this.waitSession(args);
|
||||||
|
} else if (method === 'command_new') {
|
||||||
|
return this.createCommand(args);
|
||||||
|
} else if (method === 'command_get') {
|
||||||
|
return this.getCommand(args);
|
||||||
|
} else {
|
||||||
|
throw new Error('Unsupported method');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createSession(args: any) {
|
||||||
|
let session = await axios.post(
|
||||||
|
`${this._endpoint}/connect/init`,
|
||||||
|
args,
|
||||||
|
this.getAxiosConfig(),
|
||||||
|
);
|
||||||
|
if (!session.data.ok) {
|
||||||
|
throw Error('Unable to create session: ' + JSON.stringify(session.data));
|
||||||
|
}
|
||||||
|
return session.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSession(args: { id: string }) {
|
||||||
|
if (!args.id) {
|
||||||
|
throw new Error('Invalid session id');
|
||||||
|
}
|
||||||
|
|
||||||
|
let session = await axios.get(
|
||||||
|
`${this._endpoint}/connect/` + args.id,
|
||||||
|
this.getAxiosConfig()
|
||||||
|
);
|
||||||
|
if (!session.data.ok) {
|
||||||
|
throw Error('Unable to create session: ' + JSON.stringify(session.data));
|
||||||
|
}
|
||||||
|
return session.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async waitSession(args: { id: string, lastUpdated?: number }) {
|
||||||
|
if (!args.id) {
|
||||||
|
throw new Error('Invalid session id');
|
||||||
|
}
|
||||||
|
|
||||||
|
let session = await axios.get(
|
||||||
|
`${this._endpoint}/connect/` + args.id + '/wait?lastUpdated='+(args.lastUpdated || 0),
|
||||||
|
this.getAxiosConfig()
|
||||||
|
);
|
||||||
|
if (!session.data.ok) {
|
||||||
|
throw Error('Unable to create session: ' + JSON.stringify(session.data));
|
||||||
|
}
|
||||||
|
return session.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createCommand(args: any) {
|
||||||
|
let result = await axios.post(`${this._endpoint}/connect/command`, args, this.getAxiosConfig());
|
||||||
|
if (!result.data.ok) {
|
||||||
|
throw new Error('Cannot create command: ' + JSON.stringify(result.data));
|
||||||
|
}
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
async getCommand(appk: string) {
|
||||||
|
return (await axios.get(`${this._endpoint}/connect/command/` + appk, this.getAxiosConfig())).data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
interface Transport {
|
||||||
|
call<TResult = any, TArgs = any>(method: string, args: TArgs): Promise<TResult>
|
||||||
|
}
|
Loading…
Reference in New Issue