112 lines
4.3 KiB
JavaScript
112 lines
4.3 KiB
JavaScript
import { importPkcs8Ed25519, signBase64 } from './crypto-utils.js';
|
|
import { WsJsonClient } from './ws-client.js';
|
|
|
|
const SESSION_TYPE_WALLET = 50;
|
|
|
|
function normalizeServerUrl(url) {
|
|
const value = String(url || '').trim();
|
|
if (!value) return 'wss://shineup.me/ws';
|
|
if (value.startsWith('ws://') || value.startsWith('wss://')) return value;
|
|
if (value.startsWith('http://') || value.startsWith('https://')) {
|
|
const parsed = new URL(value);
|
|
parsed.protocol = parsed.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
if (!parsed.pathname || parsed.pathname === '/') parsed.pathname = '/ws';
|
|
return parsed.toString();
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function opError(op, response) {
|
|
const payload = response?.payload || {};
|
|
const message = payload?.message || response?.message || payload?.error || response?.error || 'Unknown server error';
|
|
const code = String(payload?.code || response?.code || payload?.error || response?.error || 'UNKNOWN').toUpperCase();
|
|
const error = new Error(`${op}: ${message} (${code})`);
|
|
error.op = op;
|
|
error.code = code;
|
|
error.status = response?.status || 0;
|
|
return error;
|
|
}
|
|
|
|
export class ShineApiClient {
|
|
constructor(serverUrl) {
|
|
this.serverUrl = normalizeServerUrl(serverUrl);
|
|
this.ws = new WsJsonClient(this.serverUrl);
|
|
}
|
|
|
|
async getUser(login) {
|
|
const response = await this.ws.request('GetUser', { login: String(login || '').trim() });
|
|
if (response.status !== 200) throw opError('GetUser', response);
|
|
return response.payload || {};
|
|
}
|
|
|
|
async startEspPairing({ login, passwordHash, requesterSessionKey, payloadType = 1 }) {
|
|
const response = await this.ws.request('StartEspPairing', {
|
|
login: String(login || '').trim(),
|
|
passwordHash: String(passwordHash || '').trim(),
|
|
requesterSessionKey: String(requesterSessionKey || '').trim(),
|
|
requesterSessionType: SESSION_TYPE_WALLET,
|
|
requesterClientPlatform: 'Chrome Extension Wallet',
|
|
payloadType: Number(payloadType) || 1,
|
|
});
|
|
if (response.status !== 200) throw opError('StartEspPairing', response);
|
|
return response.payload || {};
|
|
}
|
|
|
|
async getEspPairingStatus(pairingId) {
|
|
const response = await this.ws.request('GetEspPairingStatus', {
|
|
pairingId: String(pairingId || '').trim(),
|
|
});
|
|
if (response.status !== 200) throw opError('GetEspPairingStatus', response);
|
|
return response.payload || {};
|
|
}
|
|
|
|
async cancelEspPairing(pairingId, requesterSessionKey) {
|
|
const response = await this.ws.request('CancelEspPairing', {
|
|
pairingId: String(pairingId || '').trim(),
|
|
requesterSessionKey: String(requesterSessionKey || '').trim(),
|
|
});
|
|
if (response.status !== 200) throw opError('CancelEspPairing', response);
|
|
return response.payload || {};
|
|
}
|
|
|
|
async resumeSession(sessionRecord) {
|
|
const login = String(sessionRecord?.login || '').trim();
|
|
const sessionId = String(sessionRecord?.sessionId || '').trim();
|
|
const sessionKey = String(sessionRecord?.sessionKey || '').trim();
|
|
const sessionPrivPkcs8 = String(sessionRecord?.sessionPrivPkcs8 || '').trim();
|
|
if (!login || !sessionId || !sessionKey || !sessionPrivPkcs8) {
|
|
throw new Error('Сохранённая wallet-session неполная');
|
|
}
|
|
|
|
const privateKey = await importPkcs8Ed25519(sessionPrivPkcs8);
|
|
const challengeResp = await this.ws.request('SessionChallenge', { sessionId });
|
|
if (challengeResp.status !== 200) throw opError('SessionChallenge', challengeResp);
|
|
const nonce = challengeResp?.payload?.nonce;
|
|
if (!nonce) throw new Error('SessionChallenge: сервер не вернул nonce');
|
|
|
|
const timeMs = Date.now();
|
|
const preimage = `SESSION_LOGIN:${sessionId}:${timeMs}:${nonce}`;
|
|
const signatureB64 = await signBase64(privateKey, preimage);
|
|
const loginResp = await this.ws.request('SessionLogin', {
|
|
sessionId,
|
|
sessionKey,
|
|
timeMs,
|
|
signatureB64,
|
|
sessionType: Number(sessionRecord?.sessionType || SESSION_TYPE_WALLET) || SESSION_TYPE_WALLET,
|
|
clientPlatform: 'Chrome Extension Wallet',
|
|
clientInfo: 'SHiNE Browser Plugin Wallet',
|
|
});
|
|
if (loginResp.status !== 200) throw opError('SessionLogin', loginResp);
|
|
|
|
return {
|
|
login,
|
|
sessionId,
|
|
storagePwd: String(loginResp?.payload?.storagePwd || '').trim(),
|
|
};
|
|
}
|
|
|
|
close() {
|
|
this.ws.close();
|
|
}
|
|
}
|