@airmoney-degn/controller-sdk
Version:
SDK for controlling AirMoney devices, providing button screen management, key event handling, and device communication capabilities
693 lines (692 loc) • 23.4 kB
JavaScript
var C = Object.defineProperty;
var N = (t, a, e) => a in t ? C(t, a, { enumerable: !0, configurable: !0, writable: !0, value: e }) : t[a] = e;
var s = (t, a, e) => N(t, typeof a != "symbol" ? a + "" : a, e);
class f {
constructor(a) {
s(this, "baseURL");
s(this, "serviceName");
s(this, "createEndpoint", (a) => [this.baseURL, a].join("/"));
s(this, "createPayload", (a, e) => ({
id: (/* @__PURE__ */ new Date()).getTime().toString(),
jsonrpc: "2.0",
method: a,
params: e
}));
this.baseURL = a, this.serviceName = this.constructor.name;
}
async request(a) {
return await (await fetch(this.createEndpoint(""), {
method: "POST",
body: JSON.stringify(a),
headers: {
Accept: "application/json",
"Content-Type": "application/json"
}
})).json();
}
}
const l = (t) => {
if (typeof t == "bigint" || typeof t == "number")
return `0x${t.toString(16)}`;
if (typeof t == "string")
return t.startsWith("0x") ? t : `0x${t}`;
throw new Error("Invalid input");
}, I = (t) => {
const a = {};
return t.to && (a.to = l(t.to)), t.value && (a.value = l(t.value)), t.gasLimit && (a.gasLimit = l(t.gasLimit)), t.gasPrice && (a.gasPrice = l(t.gasPrice)), t.maxFeePerGas && (a.maxFeePerGas = l(t.maxFeePerGas)), t.maxPriorityFeePerGas && (a.maxPriorityFeePerGas = l(t.maxPriorityFeePerGas)), t.nonce && (a.nonce = l(t.nonce)), t.chainId && (a.chainId = l(t.chainId)), t.data && (a.data = l(t.data)), t.type && (a.type = l(t.type)), t.gas && (a.gas = l(t.gas)), t.accessList && (a.accessList = t.accessList.map((e) => ({
address: e.address ? l(e.address) : null,
storageKeys: e.storageKeys.map((i) => l(i))
}))), a;
};
class _ extends f {
constructor() {
super("http://localhost:5050");
// EVM Operations
s(this, "signEvmMessage", async (e) => {
const i = this.createPayload("signEvmMessage", [e]);
return await this.request(i);
});
s(this, "signEvmTransaction", async (e) => {
const i = this.createPayload("signEvmTransaction", [e]);
return await this.request(i);
});
s(this, "signGeneralEvmTransaction", async (e) => {
const i = this.createPayload("signGeneralEvmTransaction", [e]);
return await this.request(i);
});
s(this, "signEip712TypedData", async (e) => {
const i = this.createPayload("signEip712TypedData", [e]);
return await this.request(i);
});
s(this, "verifyEip1271Signature", async (e) => {
const i = this.createPayload("verifyEip1271Signature", [
e.rpcUrl,
e.contractAddress,
e.message,
e.signature
]);
return await this.request(i);
});
// Solana Operations
s(this, "signSolanaMessage", async (e) => {
const i = this.createPayload("signSolanaMessage", [e]);
return await this.request(i);
});
s(this, "signSolanaTransaction", async (e) => {
const i = this.createPayload("signSolanaTransaction", [e]);
return await this.request(i);
});
s(this, "signEVMRawTransaction", async (e) => {
const i = I(e.rawTransaction), n = typeof e.chain_id == "number" ? l(e.chain_id) : e.chain_id;
return await this.signGeneralEvmTransaction({
address: e.address,
typed_tx: i,
chain_id: n,
pin: e.pin
});
});
s(this, "getDefaultEvmWallet", async () => {
const e = this.createPayload("getDefaultEvmWallet", []);
return await this.request(e);
});
s(this, "getDefaultSvmWallet", async () => {
const e = this.createPayload("getDefaultSvmWallet", []);
return await this.request(e);
});
// Bitcoin Operations
s(this, "signBitcoinMessage", async (e) => {
const i = this.createPayload("signBitcoinMessage", [e]);
return await this.request(i);
});
s(this, "verifyBitcoinSignature", async (e) => {
const i = this.createPayload("verifyBitcoinSignature", [e.address, e.message, e.signature]);
return await this.request(i);
});
s(this, "signBitcoinTransaction", async (e) => {
const i = this.createPayload("signBitcoinTransaction", [e]);
return await this.request(i);
});
s(this, "getDefaultBitcoinWallet", async () => {
const e = this.createPayload("getDefaultBitcoinWallet", []);
return await this.request(e);
});
// Device Status & PIN Operations
s(this, "getDeviceStatus", async () => {
const e = this.createPayload("getDeviceStatus", []);
return await this.request(e);
});
s(this, "verifyPin", async (e) => {
const i = this.createPayload("verifyPin", [e]);
return await this.request(i);
});
}
// Universal Wallet Operations
}
const q = (t) => "result" in t, w = (t) => "error" in t;
var D = /* @__PURE__ */ ((t) => (t.Right = "right", t.Left = "left", t))(D || {}), o = /* @__PURE__ */ ((t) => (t.CoreUnhandled = "CoreUnhandled", t.DisplayInvalidInput = "DisplayInvalidInput", t.DisplayAssetLoad = "DisplayAssetLoad", t.DisplayHardware = "DisplayHardware", t.NetworkCommand = "NetworkCommand", t.NetworkAuth = "NetworkAuth", t.NetworkConfig = "NetworkConfig", t.HapticInvalidAction = "HapticInvalidAction", t.HapticHardware = "HapticHardware", t.BluetoothControl = "BluetoothControl", t.BluetoothData = "BluetoothData", t.BrightnessFailure = "BrightnessFailure", t.AudioFailure = "AudioFailure", t.TelemetryRead = "TelemetryRead", t.TelemetryRender = "TelemetryRender", t.SessionParse = "SessionParse", t.SessionStorage = "SessionStorage", t.Unknown = "Unknown", t))(o || {});
const h = "app-launcher", G = () => {
fetch("http://127.0.0.1:7070/background", {
method: "GET"
}).catch((t) => {
console.error("Failed to return to launcher:", t);
});
}, H = () => {
fetch("http://127.0.0.1:7070/reload", {
method: "GET"
}).catch((t) => {
console.error("Failed to reload web app:", t);
});
}, A = () => window.location.protocol === "file:", x = () => window.location.hostname.endsWith(".internal"), z = (t = h) => {
window.location.href = v(t);
}, R = (t = h, a) => [v(t), a].filter(Boolean).join("/"), v = (t = h) => `http://${t}.internal`, W = (t = h) => R(t, "dapp-logo.png");
let y = null;
const T = async () => {
if (y)
return y;
try {
const t = await fetch("/metadata.json");
if (!t.ok)
throw new Error("Failed to fetch metadata.json");
const a = await t.json();
return y = a, a;
} catch (t) {
throw new Error(`Failed to load metadata: ${t instanceof Error ? t.message : "Unknown error"}`);
}
}, L = async () => {
try {
if (A())
return "file";
try {
return (await T()).name;
} catch {
throw new Error("Could not determine application identifier in unbundled environment");
}
} catch {
if (window.AIRMONEY_APP_ID === void 0)
throw new Error("Neither metadata.json nor AIRMONEY_APP_ID is available");
return window.AIRMONEY_APP_ID;
}
}, m = {
enabled: !1,
time: 40
}, S = (t) => t === "setImage" || t === "setAnimate", u = class u extends f {
constructor(e) {
super("http://localhost:4040");
s(this, "appName", "");
s(this, "isInitialized", !1);
s(this, "throttleConfigs", {});
s(this, "createThrottledMethod", (e, i) => async (...n) => {
const r = this.throttleConfigs[i];
if (!r || !r.enabled)
return e.apply(this, n);
const c = n[0].id.toString();
return new Promise((b) => {
if (r.lastArgs.set(c, n), !r.timeouts.has(c)) {
const P = setTimeout(async () => {
try {
const p = r.lastArgs.get(c);
if (p) {
const E = await e.apply(this, p);
b(E);
}
} finally {
r.timeouts.delete(c), r.lastArgs.delete(c);
}
}, r.time);
r.timeouts.set(c, P);
}
});
});
s(this, "initialize", async () => {
if (!this.isInitialized && !this.appName) {
try {
this.appName = await L();
} catch {
if (window.AIRMONEY_APP_ID === void 0)
throw new Error("Neither identifier from metadata.json nor AIRMONEY_APP_ID is available");
this.appName = window.AIRMONEY_APP_ID;
}
if (!this.appName)
throw new Error("Neither identifier from metadata.json nor AIRMONEY_APP_ID is available");
this.isInitialized = !0;
}
});
s(this, "ensureInitialized", async () => {
this.isInitialized || await this.initialize();
});
s(this, "setImage", this.createThrottledMethod(
async (e) => {
await this.ensureInitialized();
const i = this.createPayload("setImage", [
this.createImagePathParam(e.imageName),
e.id
]);
return await this.request(i);
},
"setImage"
));
s(this, "setAnimate", this.createThrottledMethod(
async (e) => {
await this.ensureInitialized();
const i = this.createPayload("setAnimate", [
this.createImagePathParam(e.imageName),
e.id
]);
return await this.request(i);
},
"setAnimate"
));
s(this, "createImagePathParam", (e) => {
if (!this.appName)
throw new Error("App name is required");
return [this.appName, e].filter(Boolean).join("/");
});
e && Object.entries(e).forEach((i) => {
const n = i[0], r = i[1];
S(n) && (this.throttleConfigs[n] = {
enabled: r.throttleEnabled ?? m.enabled,
time: r.throttleTime ?? m.time,
timeouts: /* @__PURE__ */ new Map(),
lastArgs: /* @__PURE__ */ new Map()
});
});
}
async request(e) {
const i = await super.request(e);
if (w(i)) {
const n = u.ERROR_CODE_MAP.get(i.error.code);
return {
...i,
type: (n == null ? void 0 : n.type) ?? o.Unknown,
userMessage: (n == null ? void 0 : n.userMessage) || i.error.message || "Unknown error"
};
}
return i;
}
};
s(u, "ERROR_CODE_MAP", /* @__PURE__ */ new Map([
// Core/System
[
1e3,
{
type: o.CoreUnhandled,
userMessage: "An unexpected system error occurred. Please try again."
}
],
// Display (buttons & side OLED)
[
2001,
{
type: o.DisplayInvalidInput,
userMessage: "The parameter is invalid. Please check your input and try again."
}
],
[
2002,
{
type: o.DisplayAssetLoad,
userMessage: "Unable to load or decode the image file. Please check that the image exists and is in a supported format."
}
],
[
2003,
{
type: o.DisplayHardware,
userMessage: "Display hardware error. The display mutex or SPI communication failed. Please try again."
}
],
// Network/Wi-Fi
[
3001,
{
type: o.NetworkCommand,
userMessage: "Network or system command execution failed. Please try again."
}
],
[
3002,
{
type: o.NetworkAuth,
userMessage: "Wi-Fi authentication timed out. Please check your password and try again."
}
],
[
3003,
{
type: o.NetworkConfig,
userMessage: "Failed to write network configuration. Please try again."
}
],
// Haptics
[
4001,
{
type: o.HapticInvalidAction,
userMessage: "Invalid haptic action name. Please check your input and try again."
}
],
[
4002,
{
type: o.HapticHardware,
userMessage: "Haptic hardware error occurred. Please try again."
}
],
// Bluetooth/Pairing
[
5001,
{
type: o.BluetoothControl,
userMessage: "Pairing service control failed. Please try again."
}
],
[
5002,
{
type: o.BluetoothData,
userMessage: "Failed to read or parse Bluetooth device data. Please try again."
}
],
// Audio/Brightness
[
6001,
{
type: o.BrightnessFailure,
userMessage: "Failed to adjust brightness. Hardware interaction error or missing device. Please try again."
}
],
[
6002,
{
type: o.AudioFailure,
userMessage: "Audio operation failed. Hardware interaction error or missing ALSA device. Please try again."
}
],
// Telemetry
[
7001,
{
type: o.TelemetryRead,
userMessage: "Failed to read telemetry data. Power supply paths are unreadable. Please try again."
}
],
[
7002,
{
type: o.TelemetryRender,
userMessage: "Failed to render telemetry data. Error composing status payload. Please try again."
}
],
// Session & MPC
[
8001,
{
type: o.SessionParse,
userMessage: "Failed to parse session data. Invalid JSON format. Please try again."
}
],
[
8002,
{
type: o.SessionStorage,
userMessage: "Session storage error. Filesystem read or write failed. Please try again."
}
]
]));
let g = u;
const U = new g({
setImage: {
throttleEnabled: !0
},
setAnimate: {
throttleEnabled: !0
}
}), k = (t) => async (...a) => {
const e = await t(...a);
if (w(e))
throw new Error(e.error.message || "AMService error occurred");
return e;
}, Y = k, $ = k;
var d = /* @__PURE__ */ ((t) => (t.LeftButton = "ArrowLeft", t.RightButton = "ArrowRight", t.CounterClockwiseRotary = "]", t.ClockwiseRotary = "[", t.RotaryButton = "Enter", t.SideButton = "ArrowUp", t.MuteSwitch = "ArrowDown", t))(d || {});
class O {
constructor(a) {
s(this, "config", {
threshold: 300,
combinations: void 0,
doubleClicks: void 0,
debug: !1
});
s(this, "activeKeys", {});
s(this, "timers", {});
s(this, "callbacks", []);
s(this, "window", window);
s(this, "keyUpEventName", "keyup");
s(this, "keyDownEventName", "keydown");
s(this, "getSortedCombinations", (a) => {
var i;
return (i = ((n) => this.config.combinations && Object.entries(this.config.combinations).filter(([, r]) => r.includes(n)))(a)) == null ? void 0 : i.sort(([, n], [, r]) => r.length - n.length);
});
s(this, "isCombinationComplete", (a, e) => a[1].every((i) => this.activeKeys[i] === e));
s(this, "getDoubleClick", (a) => this.config.doubleClicks && Object.entries(this.config.doubleClicks).find(([, e]) => e === a));
s(this, "debugLog", (...a) => {
this.config.debug && console.log(...a);
});
s(this, "getKeyFromEvent", (a) => {
let e = null;
return a instanceof CustomEvent && a.detail && a.detail.key && (e = a.detail.key), a instanceof KeyboardEvent && (e = a.key), !e || !Object.values(d).includes(e) ? null : e;
});
s(this, "handleCombination", (a) => {
a[1].map((e) => {
this.activeKeys[e] = "pressed", clearTimeout(this.timers[e]);
}), this.notifyCallbacks({
source: "air-money",
type: "key-event",
subType: "combinationdown",
data: {
keys: a[1],
name: a[0]
}
}), this.debugLog("---> COMBINATION DOWN", a);
});
s(this, "onKeyDown", (a) => {
const e = this.getKeyFromEvent(a);
if (e)
switch (a.preventDefault(), this.activeKeys[e]) {
case "pressed":
case "clicked":
case "end":
return;
case "over": {
Object.entries(this.activeKeys).filter(([, i]) => i === "over").forEach(([i]) => {
this.notifyCallbacks({
source: "air-money",
type: "key-event",
subType: "longpressdown",
data: {
key: i
}
}), this.debugLog("---> LONG PRESS DOWN", i);
});
return;
}
case void 0: {
const i = this.getSortedCombinations(e), n = i == null ? void 0 : i[0];
if (this.activeKeys[e] = "available", n && this.isCombinationComplete(n, "available")) {
this.handleCombination(n);
return;
} else i != null && i.length && i.length > 1 && (this.timers[e] = setTimeout(() => {
const r = i.find(
(c) => this.isCombinationComplete(c, "available")
);
r && this.handleCombination(r);
}, this.config.threshold));
this.timers[e] = setTimeout(() => {
this.notifyCallbacks({
source: "air-money",
type: "key-event",
subType: "longpressdown",
data: {
key: e
}
}), this.debugLog("---> LONG PRESS DOWN", e), this.activeKeys[e] = "over";
}, this.config.threshold);
break;
}
}
});
s(this, "onKeyUp", (a) => {
const e = this.getKeyFromEvent(a);
if (e)
switch (a.preventDefault(), this.activeKeys[e]) {
case "clicked": {
const i = this.getDoubleClick(e);
i && (this.notifyCallbacks({
source: "air-money",
type: "key-event",
subType: "doubleclick",
data: {
key: e,
name: i[0]
}
}), this.debugLog("---> DOUBLE CLICK", i), clearTimeout(this.timers[e]), delete this.activeKeys[e]);
break;
}
case "over": {
this.notifyCallbacks({
source: "air-money",
type: "key-event",
subType: "longpressup",
data: {
key: e
}
}), this.debugLog("---> LONG PRESS UP", e), clearTimeout(this.timers[e]), delete this.activeKeys[e];
break;
}
case "available": {
const i = this.getDoubleClick(e), n = this.getSortedCombinations(e), r = n == null ? void 0 : n.find(
(c) => this.isCombinationComplete(c, "available")
);
r ? this.handleCombination(r) : i ? (this.activeKeys[e] = "clicked", clearTimeout(this.timers[e]), this.timers[e] = setTimeout(() => {
this.notifyCallbacks({
source: "air-money",
type: "key-event",
subType: "press",
data: {
key: e
}
}), this.debugLog("---> PRESS", e), delete this.activeKeys[e];
}, this.config.threshold)) : (this.notifyCallbacks({
source: "air-money",
type: "key-event",
subType: "press",
data: {
key: e
}
}), this.debugLog("---> PRESS", e), clearTimeout(this.timers[e]), delete this.activeKeys[e]);
break;
}
case "pressed": {
const i = this.getSortedCombinations(e), n = i == null ? void 0 : i.find(
(r) => this.isCombinationComplete(r, "pressed")
);
n && (this.notifyCallbacks({
source: "air-money",
type: "key-event",
subType: "combinationup",
data: {
keys: n[1],
name: n[0]
}
}), this.debugLog("---> COMBINATION UP", n), n == null || n[1].map((r) => {
delete this.activeKeys[r], clearTimeout(this.activeKeys[r]);
}));
break;
}
}
});
window && (this.window = window), this.updateConfig(a), this.keyUpEventName = (a == null ? void 0 : a.keyUpEventName) || this.keyUpEventName, this.keyDownEventName = (a == null ? void 0 : a.keyDownEventName) || this.keyDownEventName;
}
updateConfig(a) {
Object.assign(this.config, a);
}
notifyCallbacks(a) {
this.callbacks.forEach((e) => e(a));
}
on(a) {
this.callbacks.push(a), this.callbacks.length === 1 && (this.window.addEventListener(this.keyDownEventName, this.onKeyDown), this.window.addEventListener(this.keyUpEventName, this.onKeyUp));
}
off(a) {
a ? this.callbacks = this.callbacks.filter((e) => e !== a) : this.callbacks = [], this.callbacks.length === 0 && (this.window.removeEventListener("keydown", this.onKeyDown), this.window.removeEventListener("keyup", this.onKeyUp));
}
getConfig() {
return this.config;
}
}
class B {
constructor(a) {
s(this, "keyEvent");
s(this, "triggerConfigs", /* @__PURE__ */ new Map());
s(this, "listenerIdCounter", 0);
s(this, "isInitialized", !1);
s(this, "debug", !1);
s(this, "initialize", () => {
if (this.isInitialized) return;
const a = (e) => {
this.handlePriorityEvent(e);
};
this.keyEvent.on(a), this.isInitialized = !0;
});
/**
* Subscribe method - registers a group of triggers with a config object.
* If config.id is missing, a unique id will be generated and assigned.
*/
s(this, "subscribe", (a) => {
this.initialize();
const e = `listener_${++this.listenerIdCounter}`;
return this.triggerConfigs.set(e, {
...a,
id: e
}), e;
});
s(this, "handlePriorityEvent", (a) => {
const e = Array.from(this.triggerConfigs.values()).sort((n, r) => r.priority - n.priority), i = [];
for (const n of e)
for (const r of n.triggers)
i.push(Object.assign(r, { name: n.name }));
this.debug && console.log(
"---> ALL TRIGGERS",
i.map((n) => n.name)
);
for (const n of i)
if (n.condition(a)) {
this.debug && console.log("---> TRIGGER", n.name), n.trigger(a);
break;
}
});
s(this, "unsubscribe", (a) => (this.triggerConfigs.delete(a), !0));
s(this, "unsubscribeAll", () => {
this.triggerConfigs.clear();
});
s(this, "getKeyEventInstance", () => this.keyEvent);
s(this, "updateConfig", (a) => {
this.keyEvent.updateConfig(a);
});
s(this, "getConfig", () => this.keyEvent.getConfig());
s(this, "destroy", () => {
this.unsubscribeAll(), this.keyEvent.off(), this.isInitialized = !1;
});
this.keyEvent = a.instance, this.debug = a.debug ?? !1;
}
}
const M = (t = {}) => {
const { combinations: a, ...e } = t;
return new O({
combinations: {
back: [d.LeftButton, d.RightButton],
backToHome: [d.LeftButton, d.RightButton, d.SideButton],
...a
},
...e
});
}, V = (t = {}) => {
const { instance: a, ...e } = t;
return new B({
instance: a ?? M(e),
...e
});
}, J = (t) => !!(t === "true" || Number(t)), Q = (t) => t !== void 0 ? /^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$/.test(t) : !1, F = (t) => t.every((a) => a !== void 0), X = (t) => {
if (F(t)) return t;
};
export {
d as AMKey,
D as AMServiceScreen,
o as AMServiceUserErrorType,
h as APP_LAUNCHER_NAME,
_ as AirMoneyCryptoService,
O as AirMoneyKeyEvent,
B as AirMoneyKeyEventManager,
g as AirMoneyService,
U as airmoneyService,
$ as airmoneyServiceErrorWrapper,
G as backToHome,
M as createDefaultKeyEvent,
V as createDefaultKeyEventManager,
Y as cryptoServiceErrorWrapper,
R as displayAsset,
k as errorWrapper,
v as getAppLink,
W as getAppLogo,
L as getAppName,
z as goToApp,
w as isAMServiceErrorResponse,
q as isAMServiceSuccessResponse,
A as isFileProtocol,
x as isInDevice,
F as isParamsValid,
Q as isValidNumber,
I as normalizeEVMTransaction,
H as reloadWebApp,
X as serializeParams,
J as toBoolean,
l as toHexString
};