Merge pull request #3 from ton-foundation/transport
draft: ton-x transport & separate from tonhub http apimain
commit
e5e6f261af
|
@ -7,6 +7,8 @@ import { delay } from "teslabot";
|
|||
import { Cell, Address, beginCell, CommentMessage, safeSign, contractAddress, safeSignVerify } from 'ton';
|
||||
import BN from 'bn.js';
|
||||
import { WalletV4Source } from 'ton-contracts';
|
||||
import { TonhubHttpTransport } from '../transport/TonhubHttpTransport';
|
||||
import { TonhubEmbeddedTransport } from '../transport/TonhubEmbeddedTransport';
|
||||
|
||||
const sessionStateCodec = t.union([
|
||||
t.type({
|
||||
|
@ -166,6 +168,15 @@ function textToCell(src: string) {
|
|||
return res;
|
||||
}
|
||||
|
||||
export function autodiscoverTransport() {
|
||||
if (TonhubEmbeddedTransport.isAvailable()) {
|
||||
return new TonhubEmbeddedTransport();
|
||||
}
|
||||
return new TonhubHttpTransport({
|
||||
endpoint: 'https://connect.tonhubapi.com'
|
||||
});
|
||||
}
|
||||
|
||||
export class TonhubConnector {
|
||||
|
||||
static extractPublicKey(config: {
|
||||
|
@ -264,22 +275,20 @@ export class TonhubConnector {
|
|||
return signed;
|
||||
}
|
||||
|
||||
readonly testnet: boolean;
|
||||
readonly adapter: any | null;
|
||||
readonly network: 'mainnet' | 'sandbox';
|
||||
readonly transport: Transport;
|
||||
|
||||
|
||||
constructor(args?: { testnet?: boolean, adapter?: any }) {
|
||||
let testnet = false;
|
||||
let adapter: any | null = null;
|
||||
constructor(args?: { network?: 'mainnet' | 'sandbox', transport?: Transport }) {
|
||||
let network: 'mainnet' | 'sandbox' = 'mainnet';
|
||||
if (args) {
|
||||
if (typeof args.testnet === 'boolean') {
|
||||
testnet = args.testnet;
|
||||
}
|
||||
if (args.adapter) {
|
||||
adapter = args.adapter;
|
||||
if (args.network !== undefined) {
|
||||
network = args.network;
|
||||
}
|
||||
}
|
||||
this.testnet = testnet;
|
||||
this.adapter = adapter;
|
||||
|
||||
this.network = network;
|
||||
this.transport = args?.transport || autodiscoverTransport();
|
||||
}
|
||||
|
||||
createNewSession = async (args: { name: string, url: string }): Promise<TonhubCreatedSession> => {
|
||||
|
@ -291,13 +300,15 @@ export class TonhubConnector {
|
|||
|
||||
// Request new session
|
||||
await backoff(async () => {
|
||||
let session = await axios.post('https://connect.tonhubapi.com/connect/init', {
|
||||
let session = await this.transport.call('session_new', {
|
||||
key: sessionId,
|
||||
testnet: this.testnet,
|
||||
testnet: this.network === 'sandbox',
|
||||
name: args.name,
|
||||
url: args.url
|
||||
}, this.adapter ? { timeout: 5000, adapter: this.adapter } : { timeout: 5000 });
|
||||
if (!session.data.ok) {
|
||||
url: args.url,
|
||||
});
|
||||
|
||||
|
||||
if (!session.ok) {
|
||||
throw Error('Unable to create state');
|
||||
}
|
||||
});
|
||||
|
@ -306,7 +317,7 @@ export class TonhubConnector {
|
|||
return {
|
||||
id: sessionId,
|
||||
seed: seed.toString('base64'),
|
||||
link: (this.testnet ? 'ton-test://connect/' : 'ton://connect/') + sessionId + '?endpoint=connect.tonhubapi.com'
|
||||
link: (this.network === 'sandbox' ? 'ton-test://connect/' : 'ton://connect/') + sessionId + '?endpoint=connect.tonhubapi.com'
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -315,7 +326,7 @@ export class TonhubConnector {
|
|||
throw Error('Invalid response from server');
|
||||
}
|
||||
if (ex.state === 'initing') {
|
||||
if (ex.testnet !== this.testnet) {
|
||||
if (ex.testnet !== (this.network === 'sandbox')) {
|
||||
return { state: 'revoked' };
|
||||
}
|
||||
return {
|
||||
|
@ -330,7 +341,7 @@ export class TonhubConnector {
|
|||
if (ex.revoked) {
|
||||
return { state: 'revoked' };
|
||||
}
|
||||
if (ex.testnet !== this.testnet) {
|
||||
if (ex.testnet !== (this.network === 'sandbox')) {
|
||||
return { state: 'revoked' };
|
||||
}
|
||||
if (!TonhubConnector.verifyWalletConfig(sessionId, ex.wallet)) {
|
||||
|
@ -359,28 +370,28 @@ export class TonhubConnector {
|
|||
|
||||
getSessionState = async (sessionId: string): Promise<TonhubSessionState> => {
|
||||
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;
|
||||
return this.ensureSessionStateCorrect(sessionId, ex);
|
||||
let session = await this.transport.call('session_get', {
|
||||
id: sessionId
|
||||
});
|
||||
return this.ensureSessionStateCorrect(sessionId, session);
|
||||
});
|
||||
}
|
||||
|
||||
waitForSessionState = async (sessionId: string, lastUpdated?: number): Promise<TonhubSessionState> => {
|
||||
return await backoff(async () => {
|
||||
let params: AxiosRequestConfig = this.adapter ? { timeout: 30000, adapter: this.adapter } : { timeout: 30000 }
|
||||
params.timeout = 30000
|
||||
params.params = {
|
||||
let session = await this.transport.call('session_wait', {
|
||||
id: sessionId,
|
||||
lastUpdated
|
||||
};
|
||||
let ex = (await axios.get('https://connect.tonhubapi.com/connect/' + sessionId + '/wait', params)).data;
|
||||
return this.ensureSessionStateCorrect(sessionId, ex);
|
||||
});
|
||||
return this.ensureSessionStateCorrect(sessionId, session);
|
||||
})
|
||||
}
|
||||
|
||||
awaitSessionReady = async (sessionId: string, timeout: number, lastUpdated: number): Promise<TonhubSessionAwaited> => {
|
||||
awaitSessionReady = async (sessionId: string, timeout: number, lastUpdated?: number): Promise<TonhubSessionAwaited> => {
|
||||
let expires = Date.now() + timeout;
|
||||
let res: TonhubSessionStateReady | TonhubSessionStateExpired | TonhubSessionStateRevoked = await backoff(async () => {
|
||||
while (Date.now() < expires) {
|
||||
let existing = await this.getSessionState(sessionId);
|
||||
let existing = await this.waitForSessionState(sessionId, lastUpdated);
|
||||
if (existing.state !== 'initing') {
|
||||
if (existing.state === 'ready') {
|
||||
return existing;
|
||||
|
@ -459,7 +470,9 @@ export class TonhubConnector {
|
|||
let boc = pkg.toBoc({ idx: false }).toString('base64');
|
||||
|
||||
// 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
|
||||
let result = await this._awaitJobState(request.appPublicKey, boc);
|
||||
|
@ -522,8 +535,11 @@ export class TonhubConnector {
|
|||
.endCell();
|
||||
let boc = pkg.toBoc({ idx: false }).toString('base64');
|
||||
|
||||
|
||||
// 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
|
||||
let result = await this._awaitJobState(request.appPublicKey, boc);
|
||||
|
@ -563,7 +579,9 @@ export class TonhubConnector {
|
|||
|
||||
private _getJobState = async (appPublicKey: string, boc: string): Promise<{ type: 'expired' | 'rejected' | 'submitted' } | { type: 'completed', result: string }> => {
|
||||
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)) {
|
||||
throw Error('Invalid response from server');
|
||||
}
|
||||
|
@ -571,7 +589,7 @@ export class TonhubConnector {
|
|||
return { type: 'expired' };
|
||||
}
|
||||
if (res.job !== boc) {
|
||||
return { type: 'rejected' };
|
||||
return { type: 'rejected' };
|
||||
}
|
||||
if (res.state === 'expired') {
|
||||
return { type: 'expired' };
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
export class TonhubEmbeddedTransport implements Transport {
|
||||
call<TResult, TArgs>(method: string, args: TArgs): Promise<TResult> {
|
||||
let tonX = TonhubEmbeddedTransport.get();
|
||||
if (!tonX) {
|
||||
throw new Error('ton-x not found');
|
||||
}
|
||||
|
||||
return tonX.call(method, args);
|
||||
}
|
||||
|
||||
private static get() {
|
||||
if (typeof window === 'undefined' || !(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.get() !== null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
import axios from 'axios';
|
||||
import { string } from 'fp-ts';
|
||||
|
||||
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(),
|
||||
);
|
||||
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()
|
||||
);
|
||||
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(), timeout: 30000 }
|
||||
);
|
||||
return session.data;
|
||||
}
|
||||
|
||||
async createCommand(args: any) {
|
||||
let result = await axios.post(`${this._endpoint}/connect/command`, args, this.getAxiosConfig());
|
||||
return result.data;
|
||||
}
|
||||
async getCommand(args: { appk: string }) {
|
||||
return (await axios.get(`${this._endpoint}/connect/command/` + args.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