Previously a rejected /proposal call cleared the LSP's error message entirely, so users saw a plain unwrapped invoice with no indication the LSP declined to wrap it (e.g. channel opens frozen around a reorg-risk block range).
1432 lines
52 KiB
TypeScript
1432 lines
52 KiB
TypeScript
import { action, observable, reaction, runInAction } from 'mobx';
|
|
import ReactNativeBlobUtil from 'react-native-blob-util';
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
|
import SettingsStore, { getLspConfigForNetwork } from './SettingsStore';
|
|
import ChannelsStore from './ChannelsStore';
|
|
import NodeInfoStore from './NodeInfoStore';
|
|
|
|
import lndMobile from '../lndmobile/LndMobileInjection';
|
|
const { index, channel } = lndMobile;
|
|
|
|
import { LSPOrderState, LSPService } from '../models/LSP';
|
|
|
|
import BackendUtils from '../utils/BackendUtils';
|
|
import Base64Utils from '../utils/Base64Utils';
|
|
import { getClientInfo } from '../utils/ClientInfoUtils';
|
|
import { LndMobileEventEmitter } from '../utils/LndMobileUtils';
|
|
import { localeString } from '../utils/LocaleUtils';
|
|
import { errorToUserFriendly } from '../utils/ErrorUtils';
|
|
|
|
import Storage from '../storage';
|
|
|
|
export const LEGACY_LSPS1_ORDERS_KEY = 'orderResponses';
|
|
export const LSPS_ORDERS_KEY = 'zeus-lsps1-orders';
|
|
|
|
const CUSTOM_MESSAGE_TYPE = 37913;
|
|
const JSON_RPC_VERSION = '2.0';
|
|
|
|
// Upper bound for how long a custom-message get_order round trip may take before
|
|
// the caller stops waiting. Resolves early as soon as the response arrives.
|
|
const CUSTOM_MESSAGE_RESPONSE_TIMEOUT_MS = 7000;
|
|
|
|
const FREE_ORDER_POLL_INTERVAL_MS = 5000;
|
|
|
|
export default class LSPStore {
|
|
@observable public info: any = {};
|
|
@observable public zeroConfFee: number | undefined;
|
|
@observable public feeId: string | undefined;
|
|
@observable public pubkey: string;
|
|
@observable public loadingLSPS1: boolean = true;
|
|
@observable public loadingLSPS7: boolean = true;
|
|
@observable public error: boolean = false;
|
|
@observable public error_msg: string = '';
|
|
@observable public flow_error: boolean = false;
|
|
@observable public flow_error_msg: string = '';
|
|
@observable public flow_warning_msg: string = '';
|
|
@observable public showLspSettings: boolean = false;
|
|
@observable public channelAcceptor: any;
|
|
@observable public customMessagesSubscriber: any;
|
|
@observable public resolvedCustomMessage: boolean;
|
|
// LSPS1
|
|
@observable public getInfoId: string;
|
|
@observable public createOrderId: string;
|
|
@observable public getOrderId: string;
|
|
@observable public getInfoData: any = {};
|
|
@observable public createOrderResponse: any = {};
|
|
@observable public getOrderResponse: any = {};
|
|
// LSPS7
|
|
@observable public getExtendableChannelsId: string;
|
|
@observable public getExtendableChannelsData: any = [];
|
|
@observable public createExtensionOrderId: string;
|
|
@observable public createExtensionOrderResponse: any = {};
|
|
@observable public getExtensionOrderId: string;
|
|
@observable public getExtensionOrderResponse: any = {};
|
|
|
|
// Resolve callbacks for in-flight custom-message get_order requests, invoked
|
|
// by handleCustomMessages when the matching response arrives so callers can
|
|
// await the round trip instead of racing a fixed delay.
|
|
private getOrderResponseResolver: (() => void) | null = null;
|
|
private getExtensionOrderResponseResolver: (() => void) | null = null;
|
|
|
|
// Free orders have no payment step, so they never pass through the LSPPaymentAwait polling screen.
|
|
// Without this poll the post-creation view would stay on the
|
|
// "opening now…" message forever, even after the channel open fails.
|
|
// Poll get_order until the order reaches a terminal state, then mirror the result
|
|
// into the create-order response so the view reflects the real outcome.
|
|
private freeOrderPollTimer: ReturnType<typeof setTimeout> | null = null;
|
|
private freeOrderPollId: string | null = null;
|
|
// Incremented on every start/stop so an in-flight poll that resumes after an
|
|
// await can tell whether its session is still the active one.
|
|
private freeOrderPollSessionId = 0;
|
|
|
|
settingsStore: SettingsStore;
|
|
channelsStore: ChannelsStore;
|
|
nodeInfoStore: NodeInfoStore;
|
|
|
|
constructor(
|
|
settingsStore: SettingsStore,
|
|
channelsStore: ChannelsStore,
|
|
nodeInfoStore: NodeInfoStore
|
|
) {
|
|
this.settingsStore = settingsStore;
|
|
this.channelsStore = channelsStore;
|
|
this.nodeInfoStore = nodeInfoStore;
|
|
|
|
reaction(
|
|
() => this.channelsStore.channels,
|
|
() => {
|
|
const lspPubkey = this.getLSPSPubkey();
|
|
if (
|
|
(BackendUtils.supportsLSPScustomMessage() ||
|
|
BackendUtils.supportsLSPS7native()) &&
|
|
lspPubkey &&
|
|
this.channelsStore.channels.some(
|
|
(channel: { remotePubkey: string }) =>
|
|
channel.remotePubkey === lspPubkey
|
|
)
|
|
) {
|
|
this.getExtendableChannels();
|
|
this.nodeInfoStore.getNodeInfo();
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
@action
|
|
public reset = (errorsOnly = false) => {
|
|
if (!errorsOnly) {
|
|
this.info = {};
|
|
this.resetFee();
|
|
this.channelAcceptor = undefined;
|
|
this.customMessagesSubscriber = undefined;
|
|
}
|
|
this.error = false;
|
|
this.error_msg = '';
|
|
this.flow_error = false;
|
|
this.flow_error_msg = '';
|
|
this.flow_warning_msg = '';
|
|
this.showLspSettings = false;
|
|
};
|
|
|
|
@action
|
|
public resetFee = () => {
|
|
this.zeroConfFee = undefined;
|
|
this.feeId = undefined;
|
|
this.flow_error = false;
|
|
this.flow_error_msg = '';
|
|
this.flow_warning_msg = '';
|
|
this.showLspSettings = false;
|
|
};
|
|
|
|
@action
|
|
public resetLSPS1Data = () => {
|
|
this.stopFreeOrderStatusPolling();
|
|
this.createOrderResponse = {};
|
|
this.getInfoData = {};
|
|
this.loadingLSPS1 = false;
|
|
this.error = false;
|
|
this.error_msg = '';
|
|
};
|
|
|
|
@action
|
|
public resetLSPS7Data = () => {
|
|
this.stopFreeOrderStatusPolling();
|
|
this.createExtensionOrderResponse = {};
|
|
this.loadingLSPS7 = false;
|
|
this.error = false;
|
|
this.error_msg = '';
|
|
};
|
|
|
|
@action
|
|
public clearLSPS7Order = () => {
|
|
this.stopFreeOrderStatusPolling();
|
|
this.createExtensionOrderResponse = {};
|
|
};
|
|
|
|
public saveOrderToHistory = async (
|
|
orderResponse: any,
|
|
service: 'LSPS1' | 'LSPS7'
|
|
): Promise<void> => {
|
|
const result = orderResponse?.result || orderResponse;
|
|
const orderId = result?.order_id;
|
|
if (!orderId) return;
|
|
|
|
try {
|
|
const responseArrayString = await Storage.getItem(LSPS_ORDERS_KEY);
|
|
const responseArray: any[] = responseArrayString
|
|
? JSON.parse(responseArrayString)
|
|
: [];
|
|
|
|
const exists = responseArray.some((response: any) => {
|
|
const stored = JSON.parse(response);
|
|
const storedId =
|
|
stored.order?.order_id || stored.order?.result?.order_id;
|
|
return storedId === orderId;
|
|
});
|
|
if (exists) return;
|
|
|
|
const orderData: any = {
|
|
order: orderResponse,
|
|
clientPubkey: this.nodeInfoStore.nodeInfo?.nodeId,
|
|
service
|
|
};
|
|
|
|
if (service === 'LSPS1' && BackendUtils.supportsLSPS1native()) {
|
|
orderData.native = true;
|
|
} else if (BackendUtils.supportsLSPScustomMessage()) {
|
|
orderData.peer = this.getLSPSPubkey();
|
|
orderData.uri = `${this.getLSPSPubkey()}@${this.getLSPSHost()}`;
|
|
}
|
|
if (service === 'LSPS1' && BackendUtils.supportsLSPS1rest()) {
|
|
orderData.endpoint = this.getLSPS1Rest();
|
|
}
|
|
|
|
responseArray.push(JSON.stringify(orderData));
|
|
await Storage.setItem(LSPS_ORDERS_KEY, responseArray);
|
|
} catch (error) {
|
|
console.error(`Error saving ${service} order:`, error);
|
|
}
|
|
};
|
|
|
|
private getLspConfig = () =>
|
|
getLspConfigForNetwork(
|
|
this.settingsStore.settings,
|
|
this.nodeInfoStore!.nodeInfo
|
|
);
|
|
|
|
public isOlympus = () => {
|
|
const config = this.getLspConfig();
|
|
if (
|
|
BackendUtils.supportsLSPScustomMessage() &&
|
|
this.getLSPSPubkey() == config.defaultPubkey
|
|
) {
|
|
return true;
|
|
} else if (
|
|
BackendUtils.supportsLSPS1rest() &&
|
|
this.getLSPS1Rest() === config.lsps1Rest
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
private getFlowHost = () => this.getLspConfig().flowHost;
|
|
|
|
public getLSPSPubkey = () => this.getLspConfig().lsps1Pubkey;
|
|
|
|
public getLSPSHost = () => this.getLspConfig().lsps1Host;
|
|
|
|
public getLSPS1Rest = () => this.getLspConfig().lsps1Rest;
|
|
|
|
private encodeMessage = (n: any) =>
|
|
Buffer.from(JSON.stringify(n)).toString('hex');
|
|
|
|
private getClientInfo = () =>
|
|
getClientInfo(
|
|
this.settingsStore.implementation,
|
|
this.nodeInfoStore?.nodeInfo?.version
|
|
);
|
|
|
|
// Flow 2.0
|
|
|
|
public getLSPInfo = () => {
|
|
return new Promise((resolve, reject) => {
|
|
ReactNativeBlobUtil.fetch(
|
|
'get',
|
|
`${this.getFlowHost()}/api/v1/info`,
|
|
{ 'Content-Type': 'application/json' }
|
|
)
|
|
.then(async (response: any) => {
|
|
const status = response.info().status;
|
|
let data: any;
|
|
try {
|
|
data = response.json();
|
|
} catch (jsonErr) {
|
|
runInAction(() => {
|
|
this.flow_error = true;
|
|
this.flow_error_msg = `${localeString(
|
|
'stores.LSPStore.error'
|
|
)}: HTTP ${status}`;
|
|
});
|
|
reject();
|
|
return;
|
|
}
|
|
if (status == 200) {
|
|
this.info = data;
|
|
resolve(this.info);
|
|
|
|
const method =
|
|
this.info.connection_methods &&
|
|
this.info.connection_methods[0];
|
|
|
|
try {
|
|
await this.channelsStore.connectPeer(
|
|
{
|
|
host: `${method.address}:${method.port}`,
|
|
node_pubkey_string: this.info.pubkey,
|
|
local_funding_amount: ''
|
|
},
|
|
false,
|
|
true,
|
|
true
|
|
);
|
|
} catch (e) {}
|
|
} else {
|
|
const errorMsg = errorToUserFriendly(data.message);
|
|
runInAction(() => {
|
|
this.flow_error = true;
|
|
this.flow_error_msg = errorMsg;
|
|
// handle LSP geoblocking :(
|
|
if (
|
|
this.error_msg.includes(
|
|
'unavailable in your country'
|
|
)
|
|
) {
|
|
this.showLspSettings = true;
|
|
}
|
|
});
|
|
reject();
|
|
}
|
|
})
|
|
.catch(() => {
|
|
runInAction(() => {
|
|
this.flow_error = true;
|
|
this.flow_error_msg = localeString(
|
|
'stores.LSPStore.connectionError'
|
|
);
|
|
this.showLspSettings = true;
|
|
});
|
|
reject();
|
|
});
|
|
});
|
|
};
|
|
|
|
@action
|
|
public getZeroConfFee = (amount_msat: number) => {
|
|
const { settings } = this.settingsStore;
|
|
|
|
return new Promise((resolve, reject) => {
|
|
ReactNativeBlobUtil.fetch(
|
|
'post',
|
|
`${this.getFlowHost()}/api/v1/fee`,
|
|
settings.lspAccessKey
|
|
? {
|
|
'Content-Type': 'application/json',
|
|
'x-auth-token': settings.lspAccessKey
|
|
}
|
|
: { 'Content-Type': 'application/json' },
|
|
JSON.stringify({
|
|
amount_msat,
|
|
pubkey: this.nodeInfoStore.nodeInfo.nodeId
|
|
})
|
|
)
|
|
.then((response: any) => {
|
|
const status = response.info().status;
|
|
let data: any;
|
|
try {
|
|
data = response.json();
|
|
} catch (jsonErr) {
|
|
runInAction(() => {
|
|
this.flow_error = true;
|
|
this.flow_error_msg = `${localeString(
|
|
'stores.LSPStore.error'
|
|
)}: HTTP ${status}`;
|
|
});
|
|
reject();
|
|
return;
|
|
}
|
|
if (status == 200) {
|
|
runInAction(() => {
|
|
this.zeroConfFee =
|
|
data.fee_amount_msat !== undefined
|
|
? Number.parseInt(
|
|
(
|
|
Number(data.fee_amount_msat) /
|
|
1000
|
|
).toString()
|
|
)
|
|
: undefined;
|
|
this.feeId = data.id;
|
|
this.flow_error = false;
|
|
});
|
|
resolve(this.zeroConfFee);
|
|
} else {
|
|
runInAction(() => {
|
|
this.flow_error = true;
|
|
this.flow_error_msg = data?.message
|
|
? `${localeString('stores.LSPStore.error')}: ${
|
|
data.message
|
|
}`
|
|
: `${localeString(
|
|
'stores.LSPStore.error'
|
|
)}: HTTP ${status}`;
|
|
});
|
|
reject();
|
|
}
|
|
})
|
|
.catch(() => {
|
|
runInAction(() => {
|
|
this.flow_error = true;
|
|
});
|
|
reject();
|
|
});
|
|
});
|
|
};
|
|
|
|
private handleChannelAcceptorEvent = async (channelAcceptRequest: any) => {
|
|
try {
|
|
const requestPubkey = Base64Utils.bytesToHex(
|
|
channelAcceptRequest.node_pubkey
|
|
);
|
|
|
|
// Only allow 0-conf chans from LSP or whitelisted peers
|
|
const isZeroConfAllowed =
|
|
this.info?.pubkey === requestPubkey ||
|
|
(this.settingsStore?.settings?.zeroConfPeers &&
|
|
this.settingsStore?.settings?.zeroConfPeers.includes(
|
|
requestPubkey
|
|
));
|
|
|
|
await channel.channelAcceptorResponse(
|
|
channelAcceptRequest.pending_chan_id,
|
|
!channelAcceptRequest.wants_zero_conf || isZeroConfAllowed,
|
|
isZeroConfAllowed && channelAcceptRequest.wants_zero_conf
|
|
);
|
|
} catch (error: any) {
|
|
console.error('handleChannelAcceptorEvent error:', error.message);
|
|
}
|
|
};
|
|
|
|
public initChannelAcceptor = async () => {
|
|
if (this.channelAcceptor) return;
|
|
if (this.settingsStore.implementation === 'embedded-lnd') {
|
|
this.channelAcceptor = LndMobileEventEmitter.addListener(
|
|
'ChannelAcceptor',
|
|
async (event: any) => {
|
|
try {
|
|
if (event?.error_code || event?.error_desc) {
|
|
console.error(
|
|
'ChannelAcceptor stream error, will retry on next fetch'
|
|
);
|
|
this.channelAcceptor?.remove();
|
|
this.channelAcceptor = null;
|
|
return;
|
|
}
|
|
|
|
if (!event?.data) return;
|
|
|
|
const channelAcceptRequest =
|
|
channel.decodeChannelAcceptRequest(event.data);
|
|
|
|
await this.handleChannelAcceptorEvent(
|
|
channelAcceptRequest
|
|
);
|
|
} catch (error: any) {
|
|
console.error(
|
|
'channel acceptance error: ' + error.message
|
|
);
|
|
}
|
|
}
|
|
);
|
|
|
|
await channel.channelAcceptor();
|
|
} else {
|
|
// Only allow 0-conf chans from LSP or whitelisted peers
|
|
|
|
BackendUtils.initChanAcceptor({
|
|
zeroConfPeers: this.settingsStore?.settings?.zeroConfPeers,
|
|
lspPubkey: this.info?.pubkey
|
|
});
|
|
}
|
|
};
|
|
|
|
@action
|
|
public getZeroConfInvoice = (bolt11: string) => {
|
|
this.flow_error = false;
|
|
this.flow_error_msg = '';
|
|
this.flow_warning_msg = '';
|
|
this.showLspSettings = false;
|
|
|
|
const { settings } = this.settingsStore;
|
|
|
|
return new Promise((resolve, reject) => {
|
|
ReactNativeBlobUtil.fetch(
|
|
'post',
|
|
`${this.getFlowHost()}/api/v1/proposal`,
|
|
settings.lspAccessKey
|
|
? {
|
|
'Content-Type': 'application/json',
|
|
'x-auth-token': settings.lspAccessKey
|
|
}
|
|
: { 'Content-Type': 'application/json' },
|
|
JSON.stringify({
|
|
bolt11,
|
|
fee_id: this.feeId,
|
|
simpleTaproot:
|
|
settings.requestSimpleTaproot &&
|
|
BackendUtils.supportsSimpleTaprootChannels(),
|
|
client_info: this.getClientInfo()
|
|
})
|
|
)
|
|
.then(async (response: any) => {
|
|
const status = response.info().status;
|
|
let data: any;
|
|
try {
|
|
data = response.json();
|
|
} catch (jsonErr) {
|
|
runInAction(() => {
|
|
this.flow_error = true;
|
|
this.flow_error_msg = `${localeString(
|
|
'stores.LSPStore.error'
|
|
)}: HTTP ${status}`;
|
|
});
|
|
reject();
|
|
return;
|
|
}
|
|
if (status == 200 || status == 201) {
|
|
resolve(data.jit_bolt11);
|
|
} else {
|
|
runInAction(() => {
|
|
this.flow_error = true;
|
|
this.flow_error_msg = data?.message
|
|
? `${localeString('stores.LSPStore.error')}: ${
|
|
data.message
|
|
}`
|
|
: `${localeString(
|
|
'stores.LSPStore.error'
|
|
)}: HTTP ${status}`;
|
|
if (
|
|
data?.message &&
|
|
data.message.includes('access key')
|
|
) {
|
|
this.showLspSettings = true;
|
|
}
|
|
});
|
|
reject();
|
|
}
|
|
})
|
|
.catch(() => {
|
|
runInAction(() => {
|
|
this.flow_error = true;
|
|
});
|
|
reject();
|
|
});
|
|
});
|
|
};
|
|
|
|
// LSPS0
|
|
|
|
@action
|
|
public sendCustomMessage = ({
|
|
peer,
|
|
type,
|
|
data
|
|
}: {
|
|
peer: string;
|
|
type: number | null;
|
|
data: string;
|
|
}) => {
|
|
return new Promise((resolve, reject) => {
|
|
if (!peer || !type || !data) {
|
|
reject('Invalid parameters for custom message.');
|
|
return;
|
|
}
|
|
|
|
BackendUtils.sendCustomMessage({ peer, type, data })
|
|
.then((response: any) => {
|
|
resolve(response);
|
|
})
|
|
.catch((error: any) => {
|
|
const errorMsg = errorToUserFriendly(error);
|
|
runInAction(() => {
|
|
this.error = true;
|
|
this.error_msg = errorMsg;
|
|
});
|
|
reject(error);
|
|
});
|
|
});
|
|
};
|
|
|
|
@action
|
|
public handleCustomMessages = (decoded: any): boolean => {
|
|
if (!decoded?.peer || !decoded?.data) return false;
|
|
let data: any;
|
|
try {
|
|
data = JSON.parse(Base64Utils.base64ToUtf8(decoded.data));
|
|
} catch (parseError: any) {
|
|
console.warn(
|
|
'Custom message data is not valid JSON, skipping:',
|
|
parseError?.message
|
|
);
|
|
return false;
|
|
}
|
|
const peer = Base64Utils.base64ToHex(decoded.peer);
|
|
|
|
console.log('Received custom message', { peer, data });
|
|
|
|
if (data.id === this.getInfoId) {
|
|
this.getInfoData = data;
|
|
this.loadingLSPS1 = false;
|
|
return true;
|
|
} else if (data.id === this.createOrderId) {
|
|
if (data.error) {
|
|
this.error = true;
|
|
this.error_msg = data?.error?.data?.message
|
|
? errorToUserFriendly(data?.error?.data?.message)
|
|
: '';
|
|
} else {
|
|
this.createOrderResponse = data;
|
|
}
|
|
this.loadingLSPS1 = false;
|
|
return true;
|
|
} else if (data.id === this.getOrderId) {
|
|
if (data.error) {
|
|
this.error = true;
|
|
this.error_msg = data?.error?.message
|
|
? errorToUserFriendly(data?.error?.message)
|
|
: '';
|
|
} else {
|
|
this.getOrderResponse = data;
|
|
}
|
|
this.loadingLSPS1 = false;
|
|
this.getOrderResponseResolver?.();
|
|
return true;
|
|
} else if (data.id === this.getExtendableChannelsId) {
|
|
if (data.error) {
|
|
this.error = true;
|
|
this.error_msg = data?.error?.message
|
|
? errorToUserFriendly(data?.error?.message)
|
|
: '';
|
|
} else {
|
|
this.getExtendableChannelsData =
|
|
data?.result?.extendable_channels;
|
|
}
|
|
this.loadingLSPS7 = false;
|
|
return true;
|
|
} else if (data.id === this.createExtensionOrderId) {
|
|
if (data.error) {
|
|
this.error = true;
|
|
this.error_msg = data?.error?.data?.message
|
|
? errorToUserFriendly(data?.error?.data?.message)
|
|
: '';
|
|
} else {
|
|
this.createExtensionOrderResponse = data;
|
|
}
|
|
this.loadingLSPS7 = false;
|
|
return true;
|
|
} else if (data.id === this.getExtensionOrderId) {
|
|
if (data.error) {
|
|
this.error = true;
|
|
this.error_msg = data?.error?.data?.message
|
|
? errorToUserFriendly(data?.error?.data?.message)
|
|
: '';
|
|
} else {
|
|
this.getExtensionOrderResponse = data;
|
|
}
|
|
this.loadingLSPS7 = false;
|
|
this.getExtensionOrderResponseResolver?.();
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
@action
|
|
public subscribeCustomMessages = async () => {
|
|
if (this.customMessagesSubscriber) return;
|
|
this.resolvedCustomMessage = false;
|
|
let timer = 7000;
|
|
const timeoutId = setTimeout(() => {
|
|
if (!this.resolvedCustomMessage) {
|
|
runInAction(() => {
|
|
this.error = true;
|
|
this.error_msg = localeString('views.LSPS1.timeoutError');
|
|
this.loadingLSPS1 = false;
|
|
this.loadingLSPS7 = false;
|
|
});
|
|
}
|
|
}, timer);
|
|
|
|
if (this.settingsStore.implementation === 'embedded-lnd') {
|
|
this.customMessagesSubscriber = LndMobileEventEmitter.addListener(
|
|
'SubscribeCustomMessages',
|
|
async (event: any) => {
|
|
if (!event?.data) return;
|
|
try {
|
|
const decoded = index.decodeCustomMessage(event.data);
|
|
const handled = runInAction(() =>
|
|
this.handleCustomMessages(decoded)
|
|
);
|
|
if (handled) {
|
|
this.resolvedCustomMessage = true;
|
|
clearTimeout(timeoutId);
|
|
}
|
|
} catch (error: any) {
|
|
console.error(
|
|
'sub custom messages error: ' + error.message
|
|
);
|
|
}
|
|
}
|
|
);
|
|
|
|
await index.subscribeCustomMessages();
|
|
} else {
|
|
BackendUtils.subscribeCustomMessages(
|
|
(response: any) => {
|
|
const decoded = response.result;
|
|
const handled = runInAction(() =>
|
|
this.handleCustomMessages(decoded)
|
|
);
|
|
if (handled) {
|
|
this.resolvedCustomMessage = true;
|
|
clearTimeout(timeoutId);
|
|
}
|
|
},
|
|
(error: any) => {
|
|
console.error(
|
|
'sub custom messages error: ' + error.message
|
|
);
|
|
}
|
|
);
|
|
}
|
|
};
|
|
|
|
// LSPS1
|
|
|
|
public lsps1GetInfoREST = () => {
|
|
this.loadingLSPS1 = true;
|
|
|
|
const endpoint = `${this.getLSPS1Rest()}/api/v1/get_info`;
|
|
|
|
console.log('Fetching data from:', endpoint);
|
|
|
|
return ReactNativeBlobUtil.fetch('GET', endpoint)
|
|
.then((response) => {
|
|
runInAction(() => {
|
|
if (response.info().status === 200) {
|
|
const responseData = JSON.parse(response.data);
|
|
this.getInfoData = responseData;
|
|
try {
|
|
const uri = responseData.uris[0];
|
|
const pubkey = uri.split('@')[0];
|
|
this.pubkey = pubkey;
|
|
} catch (e) {}
|
|
this.loadingLSPS1 = false;
|
|
} else {
|
|
this.error = true;
|
|
this.error_msg = 'Error fetching get_info data';
|
|
this.loadingLSPS1 = false;
|
|
}
|
|
});
|
|
})
|
|
.catch(() => {
|
|
runInAction(() => {
|
|
this.error = true;
|
|
this.error_msg = 'Error fetching get_info data';
|
|
this.loadingLSPS1 = false;
|
|
});
|
|
});
|
|
};
|
|
|
|
@action
|
|
public lsps1GetInfoCustomMessage = () => {
|
|
this.loadingLSPS1 = true;
|
|
this.error = false;
|
|
this.error_msg = '';
|
|
|
|
this.getInfoId = uuidv4();
|
|
const method = 'lsps1.get_info';
|
|
|
|
this.sendCustomMessage({
|
|
peer: this.getLSPSPubkey(),
|
|
type: CUSTOM_MESSAGE_TYPE,
|
|
data: this.encodeMessage({
|
|
jsonrpc: JSON_RPC_VERSION,
|
|
method,
|
|
params: {},
|
|
id: this.getInfoId
|
|
})
|
|
})
|
|
.then((response) => {
|
|
console.log(
|
|
`Response for custom message (${method}) received:`,
|
|
response
|
|
);
|
|
})
|
|
.catch((error) => {
|
|
console.error(
|
|
`Error sending (${method}) custom message:`,
|
|
error
|
|
);
|
|
});
|
|
};
|
|
|
|
@action
|
|
public lsps1CreateOrderREST = (state: any) => {
|
|
this.loadingLSPS1 = true;
|
|
const data = JSON.stringify({
|
|
lsp_balance_sat: state.lspBalanceSat.toString(),
|
|
client_balance_sat: state.clientBalanceSat.toString(),
|
|
required_channel_confirmations: parseInt(
|
|
state.requiredChannelConfirmations
|
|
),
|
|
funding_confirms_within_blocks: parseInt(
|
|
state.confirmsWithinBlocks
|
|
),
|
|
channel_expiry_blocks: parseInt(state.channelExpiryBlocks),
|
|
token: state.token,
|
|
refund_onchain_address: state.refundOnchainAddress,
|
|
announce_channel: state.announceChannel,
|
|
public_key: this.nodeInfoStore.nodeInfo.nodeId,
|
|
client_info: this.getClientInfo()
|
|
});
|
|
this.error = false;
|
|
this.error_msg = '';
|
|
const endpoint = `${this.getLSPS1Rest()}/api/v1/create_order`;
|
|
console.log('Sending data to:', endpoint);
|
|
|
|
return ReactNativeBlobUtil.fetch(
|
|
'POST',
|
|
endpoint,
|
|
{ 'Content-Type': 'application/json' },
|
|
data
|
|
)
|
|
.then((response) => {
|
|
const responseData = JSON.parse(response.data);
|
|
if (responseData.error) {
|
|
const errorMsg = errorToUserFriendly(responseData.message);
|
|
runInAction(() => {
|
|
this.error = true;
|
|
this.error_msg = errorMsg;
|
|
this.loadingLSPS1 = false;
|
|
});
|
|
} else {
|
|
runInAction(() => {
|
|
this.createOrderResponse = responseData;
|
|
this.loadingLSPS1 = false;
|
|
console.log('Response received:', responseData);
|
|
});
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error(
|
|
'Error sending (create_order) custom message:',
|
|
error
|
|
);
|
|
const errorMsg = errorToUserFriendly(error);
|
|
runInAction(() => {
|
|
this.error = true;
|
|
this.error_msg = errorMsg;
|
|
this.loadingLSPS1 = false;
|
|
});
|
|
});
|
|
};
|
|
|
|
@action
|
|
public lsps1CreateOrderCustomMessage = (state: any) => {
|
|
this.loadingLSPS1 = true;
|
|
this.error = false;
|
|
this.error_msg = '';
|
|
|
|
this.createOrderId = uuidv4();
|
|
const method = 'lsps1.create_order';
|
|
|
|
this.sendCustomMessage({
|
|
peer: this.getLSPSPubkey(),
|
|
type: CUSTOM_MESSAGE_TYPE,
|
|
data: this.encodeMessage({
|
|
jsonrpc: JSON_RPC_VERSION,
|
|
method,
|
|
params: {
|
|
lsp_balance_sat: state.lspBalanceSat.toString(),
|
|
client_balance_sat: state.clientBalanceSat.toString(),
|
|
required_channel_confirmations: parseInt(
|
|
state.requiredChannelConfirmations
|
|
),
|
|
funding_confirms_within_blocks: parseInt(
|
|
state.confirmsWithinBlocks
|
|
),
|
|
channel_expiry_blocks: state.channelExpiryBlocks,
|
|
token: state.token,
|
|
refund_onchain_address: state.refundOnchainAddress,
|
|
announce_channel: state.announceChannel,
|
|
client_info: this.getClientInfo()
|
|
},
|
|
id: this.createOrderId
|
|
})
|
|
})
|
|
.then((response) => {
|
|
console.log(
|
|
`Response for custom message (${method}) received:`,
|
|
response
|
|
);
|
|
})
|
|
.catch((error) => {
|
|
console.error(
|
|
`Error sending (${method}) custom message:`,
|
|
error
|
|
);
|
|
});
|
|
};
|
|
|
|
public lsps1GetOrderREST(id: string, RESTHost: string) {
|
|
this.loadingLSPS1 = true;
|
|
const endpoint = `${RESTHost}/api/v1/get_order?order_id=${id}`;
|
|
|
|
console.log('Sending data to:', endpoint);
|
|
|
|
return ReactNativeBlobUtil.fetch('GET', endpoint, {
|
|
'Content-Type': 'application/json'
|
|
})
|
|
.then((response) => {
|
|
const responseData = JSON.parse(response.data);
|
|
console.log('Response received:', responseData);
|
|
runInAction(() => {
|
|
if (responseData.error) {
|
|
this.error = true;
|
|
this.error_msg = responseData.message;
|
|
} else {
|
|
this.getOrderResponse = responseData;
|
|
}
|
|
this.loadingLSPS1 = false;
|
|
});
|
|
})
|
|
.catch((error) => {
|
|
console.error('Error sending custom message:', error);
|
|
const errorMsg = errorToUserFriendly(error);
|
|
runInAction(() => {
|
|
this.error = true;
|
|
this.error_msg = errorMsg;
|
|
this.loadingLSPS1 = false;
|
|
});
|
|
});
|
|
}
|
|
|
|
@action
|
|
public lsps1GetOrderCustomMessage(
|
|
orderId: string,
|
|
peer: string
|
|
): Promise<void> {
|
|
this.loadingLSPS1 = true;
|
|
|
|
this.getOrderId = uuidv4();
|
|
const method = 'lsps1.get_order';
|
|
|
|
return new Promise<void>((resolve) => {
|
|
let settled = false;
|
|
const settle = () => {
|
|
if (settled) return;
|
|
settled = true;
|
|
clearTimeout(timeoutId);
|
|
this.getOrderResponseResolver = null;
|
|
resolve();
|
|
};
|
|
const timeoutId = setTimeout(
|
|
settle,
|
|
CUSTOM_MESSAGE_RESPONSE_TIMEOUT_MS
|
|
);
|
|
this.getOrderResponseResolver = settle;
|
|
|
|
this.sendCustomMessage({
|
|
peer,
|
|
type: CUSTOM_MESSAGE_TYPE,
|
|
data: this.encodeMessage({
|
|
jsonrpc: JSON_RPC_VERSION,
|
|
method,
|
|
params: {
|
|
order_id: orderId
|
|
},
|
|
id: this.getOrderId
|
|
})
|
|
})
|
|
.then((response) => {
|
|
console.log(
|
|
`Response for custom message (${method}) received:`,
|
|
response
|
|
);
|
|
})
|
|
.catch((error) => {
|
|
console.error(
|
|
`Error sending (${method}) custom message:`,
|
|
error
|
|
);
|
|
settle();
|
|
});
|
|
});
|
|
}
|
|
|
|
// LSPS1 Native (for embedded LDK Node)
|
|
|
|
@action
|
|
public lsps1GetInfoNative = () => {
|
|
// For native LSPS1, the LSP is configured at node initialization
|
|
// LDK Node doesn't expose a getInfo method, so we provide sensible defaults
|
|
// that allow the user to configure their channel request
|
|
this.loadingLSPS1 = false;
|
|
this.error = false;
|
|
this.error_msg = '';
|
|
// Provide default options that work with most LSPs
|
|
this.getInfoData = {
|
|
native: true,
|
|
options: {
|
|
// Minimum/maximum LSP balance (channel size from LSP side)
|
|
min_initial_lsp_balance_sat: '100000', // 100k sats minimum
|
|
max_initial_lsp_balance_sat: '100000000', // 1 BTC maximum
|
|
// Minimum/maximum client balance (push amount)
|
|
min_initial_client_balance_sat: '0',
|
|
max_initial_client_balance_sat: '0', // Most LSPs don't allow client balance
|
|
// Channel expiry options
|
|
min_channel_expiry_blocks: 4380, // ~1 month
|
|
max_channel_expiry_blocks: 52560, // ~1 year
|
|
// Confirmation requirements
|
|
min_required_channel_confirmations: 0,
|
|
max_required_channel_confirmations: 6,
|
|
min_funding_confirms_within_blocks: 6,
|
|
max_funding_confirms_within_blocks: 144
|
|
}
|
|
};
|
|
};
|
|
|
|
@action
|
|
public lsps1CreateOrderNative = async (state: any) => {
|
|
this.loadingLSPS1 = true;
|
|
this.error = false;
|
|
this.error_msg = '';
|
|
|
|
try {
|
|
const response = await BackendUtils.requestLsps1Liquidity({
|
|
amount_sat: parseInt(state.lspBalanceSat),
|
|
client_balance_sat: parseInt(state.clientBalanceSat) || 0,
|
|
expiry_blocks: parseInt(state.channelExpiryBlocks),
|
|
announce_channel: state.announceChannel || false
|
|
});
|
|
|
|
runInAction(() => {
|
|
// Transform response to match expected format
|
|
this.createOrderResponse = {
|
|
result: {
|
|
order_id: response.order_id,
|
|
lsp_balance_sat: response.lsp_balance_sat?.toString(),
|
|
client_balance_sat:
|
|
response.client_balance_sat?.toString(),
|
|
funding_confirms_within_blocks:
|
|
response.funding_confirms_within_blocks,
|
|
channel_expiry_blocks: response.channel_expiry_blocks,
|
|
payment: {
|
|
state: response.payment?.state,
|
|
fee_total_sat:
|
|
response.payment?.fee_total_sat?.toString(),
|
|
order_total_sat:
|
|
response.payment?.order_total_sat?.toString(),
|
|
bolt11_invoice: response.payment?.bolt11_invoice,
|
|
onchain_address: response.payment?.onchain_address,
|
|
onchain_payment: response.payment?.onchain_address
|
|
? {
|
|
address: response.payment.onchain_address,
|
|
order_total_sat:
|
|
response.payment.onchain_total_sat?.toString()
|
|
}
|
|
: undefined
|
|
},
|
|
channel: response.channel
|
|
}
|
|
};
|
|
this.loadingLSPS1 = false;
|
|
});
|
|
} catch (err: any) {
|
|
const errorMessage =
|
|
err?.message || err?.toString() || 'Unknown error';
|
|
runInAction(() => {
|
|
this.error = true;
|
|
this.error_msg = errorToUserFriendly(errorMessage);
|
|
this.loadingLSPS1 = false;
|
|
});
|
|
}
|
|
};
|
|
|
|
@action
|
|
public lsps1GetOrderNative = async (orderId: string) => {
|
|
this.loadingLSPS1 = true;
|
|
this.error = false;
|
|
this.error_msg = '';
|
|
|
|
try {
|
|
const response = await BackendUtils.checkLsps1OrderStatus(orderId);
|
|
|
|
runInAction(() => {
|
|
// Transform response to match expected format
|
|
this.getOrderResponse = {
|
|
result: {
|
|
order_id: response.order_id,
|
|
lsp_balance_sat: response.lsp_balance_sat?.toString(),
|
|
client_balance_sat:
|
|
response.client_balance_sat?.toString(),
|
|
payment: {
|
|
state: response.payment?.state,
|
|
fee_total_sat:
|
|
response.payment?.fee_total_sat?.toString(),
|
|
order_total_sat:
|
|
response.payment?.order_total_sat?.toString(),
|
|
bolt11_invoice: response.payment?.bolt11_invoice,
|
|
bolt11_state: response.payment?.bolt11_state,
|
|
onchain_address: response.payment?.onchain_address,
|
|
onchain_state: response.payment?.onchain_state,
|
|
onchain_payment: response.payment?.onchain_address
|
|
? {
|
|
address: response.payment.onchain_address,
|
|
state: response.payment.onchain_state
|
|
}
|
|
: undefined
|
|
},
|
|
channel: response.channel
|
|
}
|
|
};
|
|
this.loadingLSPS1 = false;
|
|
});
|
|
} catch (err: any) {
|
|
const errorMessage =
|
|
err?.message || err?.toString() || 'Unknown error';
|
|
runInAction(() => {
|
|
this.error = true;
|
|
this.error_msg = errorToUserFriendly(errorMessage);
|
|
this.loadingLSPS1 = false;
|
|
});
|
|
}
|
|
};
|
|
|
|
// LSPS7
|
|
|
|
@action
|
|
public getExtendableChannels = async () => {
|
|
this.error = false;
|
|
this.error_msg = '';
|
|
|
|
// Use native LSPS7 method if available (LDK Node backend)
|
|
if (BackendUtils.supportsLSPS7native()) {
|
|
try {
|
|
const channels =
|
|
await BackendUtils.lsps7GetExtendableChannels();
|
|
// Normalize camelCase keys from native module to snake_case
|
|
// to match the JSON-RPC custom message format
|
|
runInAction(() => {
|
|
this.getExtendableChannelsData = channels.map(
|
|
(ch: any) => ({
|
|
short_channel_id: ch.shortChannelId,
|
|
max_channel_extension_expiry_blocks:
|
|
ch.maxChannelExtensionExpiryBlocks,
|
|
expiration_block: ch.expirationBlock,
|
|
original_order: ch.originalOrder,
|
|
extension_order_ids: ch.extensionOrderIds
|
|
})
|
|
);
|
|
this.loadingLSPS7 = false;
|
|
});
|
|
return;
|
|
} catch (error: any) {
|
|
runInAction(() => {
|
|
this.error = true;
|
|
this.error_msg =
|
|
error?.message ||
|
|
localeString('error.getExtendableChannels');
|
|
this.loadingLSPS7 = false;
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Fall back to custom message for other backends
|
|
this.getExtendableChannelsId = uuidv4();
|
|
const method = 'lsps7.get_extendable_channels';
|
|
|
|
this.sendCustomMessage({
|
|
peer: this.getLSPSPubkey(),
|
|
type: CUSTOM_MESSAGE_TYPE,
|
|
data: this.encodeMessage({
|
|
jsonrpc: JSON_RPC_VERSION,
|
|
method,
|
|
params: {},
|
|
id: this.getExtendableChannelsId
|
|
})
|
|
})
|
|
.then((response) => {
|
|
console.log(`Custom message (${method}) sent:`, response);
|
|
})
|
|
.catch((error) => {
|
|
console.error(
|
|
`Error sending (${method}) custom message:`,
|
|
error
|
|
);
|
|
});
|
|
};
|
|
|
|
@action
|
|
public lsps7CreateOrderCustomMessage = async (state: any) => {
|
|
this.loadingLSPS7 = true;
|
|
this.error = false;
|
|
this.error_msg = '';
|
|
|
|
// Use native LSPS7 method if available (LDK Node backend)
|
|
if (BackendUtils.supportsLSPS7native()) {
|
|
try {
|
|
const response = await BackendUtils.lsps7CreateOrder({
|
|
shortChannelId: state.chanId,
|
|
channelExtensionExpiryBlocks: state.channelExtensionBlocks,
|
|
token: state.token,
|
|
refundOnchainAddress: state.refundOnchainAddress
|
|
});
|
|
runInAction(() => {
|
|
this.createExtensionOrderResponse = { result: response };
|
|
this.loadingLSPS7 = false;
|
|
});
|
|
return;
|
|
} catch (error: any) {
|
|
runInAction(() => {
|
|
this.error = true;
|
|
this.error_msg =
|
|
error?.message || localeString('error.createOrder');
|
|
this.loadingLSPS7 = false;
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Fall back to custom message for other backends
|
|
this.createExtensionOrderId = uuidv4();
|
|
const method = 'lsps7.create_order';
|
|
|
|
this.sendCustomMessage({
|
|
peer: this.getLSPSPubkey(),
|
|
type: CUSTOM_MESSAGE_TYPE,
|
|
data: this.encodeMessage({
|
|
jsonrpc: JSON_RPC_VERSION,
|
|
method,
|
|
params: {
|
|
short_channel_id: state.chanId,
|
|
channel_extension_expiry_blocks:
|
|
state.channelExtensionBlocks,
|
|
token: state.token,
|
|
refund_onchain_address: state.refundOnchainAddress,
|
|
client_info: this.getClientInfo()
|
|
},
|
|
id: this.createExtensionOrderId
|
|
})
|
|
})
|
|
.then((response) => {
|
|
console.log(
|
|
`Response for custom message (${method}) received:`,
|
|
response
|
|
);
|
|
})
|
|
.catch((error) => {
|
|
console.error(
|
|
`Error sending (${method}) custom message:`,
|
|
error
|
|
);
|
|
});
|
|
};
|
|
|
|
@action
|
|
public lsps7GetOrderCustomMessage = async (
|
|
orderId: string,
|
|
peer: string
|
|
) => {
|
|
this.loadingLSPS7 = true;
|
|
|
|
// Use native LSPS7 method if available (LDK Node backend)
|
|
if (BackendUtils.supportsLSPS7native()) {
|
|
try {
|
|
const response = await BackendUtils.lsps7CheckOrderStatus(
|
|
orderId
|
|
);
|
|
runInAction(() => {
|
|
this.getExtensionOrderResponse = { result: response };
|
|
this.loadingLSPS7 = false;
|
|
});
|
|
return;
|
|
} catch (error: any) {
|
|
runInAction(() => {
|
|
this.error = true;
|
|
this.error_msg =
|
|
error?.message || localeString('error.getOrderStatus');
|
|
this.loadingLSPS7 = false;
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Fall back to custom message for other backends
|
|
this.getExtensionOrderId = uuidv4();
|
|
const method = 'lsps7.get_order';
|
|
|
|
await new Promise<void>((resolve) => {
|
|
let settled = false;
|
|
const settle = () => {
|
|
if (settled) return;
|
|
settled = true;
|
|
clearTimeout(timeoutId);
|
|
this.getExtensionOrderResponseResolver = null;
|
|
resolve();
|
|
};
|
|
const timeoutId = setTimeout(
|
|
settle,
|
|
CUSTOM_MESSAGE_RESPONSE_TIMEOUT_MS
|
|
);
|
|
this.getExtensionOrderResponseResolver = settle;
|
|
|
|
this.sendCustomMessage({
|
|
peer,
|
|
type: CUSTOM_MESSAGE_TYPE,
|
|
data: this.encodeMessage({
|
|
jsonrpc: JSON_RPC_VERSION,
|
|
method,
|
|
params: {
|
|
order_id: orderId
|
|
},
|
|
id: this.getExtensionOrderId
|
|
})
|
|
})
|
|
.then((response) => {
|
|
console.log(
|
|
`Response for custom message (${method}) received:`,
|
|
response
|
|
);
|
|
})
|
|
.catch((error) => {
|
|
console.error(
|
|
`Error sending (${method}) custom message:`,
|
|
error
|
|
);
|
|
settle();
|
|
});
|
|
});
|
|
};
|
|
|
|
public startFreeOrderStatusPolling = (
|
|
orderId: string,
|
|
service: LSPService
|
|
) => {
|
|
if (this.freeOrderPollId === orderId) return;
|
|
this.stopFreeOrderStatusPolling();
|
|
this.freeOrderPollId = orderId;
|
|
const sessionId = ++this.freeOrderPollSessionId;
|
|
this.pollFreeOrderStatus(orderId, service, sessionId);
|
|
};
|
|
|
|
public stopFreeOrderStatusPolling = () => {
|
|
this.freeOrderPollId = null;
|
|
this.freeOrderPollSessionId++;
|
|
if (this.freeOrderPollTimer) {
|
|
clearTimeout(this.freeOrderPollTimer);
|
|
this.freeOrderPollTimer = null;
|
|
}
|
|
};
|
|
|
|
private pollFreeOrderStatus = async (
|
|
orderId: string,
|
|
service: LSPService,
|
|
sessionId: number
|
|
) => {
|
|
if (this.freeOrderPollSessionId !== sessionId) return;
|
|
|
|
try {
|
|
if (service === LSPService.LSPS7) {
|
|
await this.lsps7GetOrderCustomMessage(
|
|
orderId,
|
|
this.getLSPSPubkey()
|
|
);
|
|
} else if (BackendUtils.supportsLSPS1rest()) {
|
|
await this.lsps1GetOrderREST(orderId, this.getLSPS1Rest());
|
|
} else if (BackendUtils.supportsLSPScustomMessage()) {
|
|
await this.lsps1GetOrderCustomMessage(
|
|
orderId,
|
|
this.getLSPSPubkey()
|
|
);
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
if (this.freeOrderPollSessionId !== sessionId) return;
|
|
|
|
const response =
|
|
service === LSPService.LSPS7
|
|
? this.getExtensionOrderResponse
|
|
: this.getOrderResponse;
|
|
if (response && Object.keys(response).length > 0) {
|
|
const result = response?.result || response;
|
|
if (
|
|
result?.order_state === LSPOrderState.COMPLETED ||
|
|
result?.order_state === LSPOrderState.FAILED
|
|
) {
|
|
runInAction(() => {
|
|
if (service === LSPService.LSPS7) {
|
|
this.createExtensionOrderResponse = response;
|
|
} else {
|
|
this.createOrderResponse = response;
|
|
}
|
|
});
|
|
this.updateOrderInStorage(response);
|
|
this.stopFreeOrderStatusPolling();
|
|
return;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('LSPStore: free order poll error', error);
|
|
}
|
|
|
|
if (this.freeOrderPollSessionId !== sessionId) return;
|
|
this.freeOrderPollTimer = setTimeout(
|
|
() => this.pollFreeOrderStatus(orderId, service, sessionId),
|
|
FREE_ORDER_POLL_INTERVAL_MS
|
|
);
|
|
};
|
|
|
|
public updateOrderInStorage(order: any) {
|
|
Storage.getItem(LSPS_ORDERS_KEY)
|
|
.then((responseArrayString) => {
|
|
if (!responseArrayString) return;
|
|
const responseArray = JSON.parse(responseArrayString);
|
|
const index = responseArray.findIndex((response: any) => {
|
|
const decodedResponse = JSON.parse(response);
|
|
const result =
|
|
decodedResponse?.order?.result ||
|
|
decodedResponse?.order;
|
|
const currentOrderResult = order?.result || order;
|
|
return result?.order_id === currentOrderResult?.order_id;
|
|
});
|
|
if (index !== -1) {
|
|
const oldOrder = JSON.parse(responseArray[index]);
|
|
oldOrder.order = order;
|
|
responseArray[index] = JSON.stringify(oldOrder);
|
|
Storage.setItem(LSPS_ORDERS_KEY, responseArray);
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error(
|
|
'LSPStore: error updating order in storage',
|
|
error
|
|
);
|
|
});
|
|
}
|
|
}
|