@bwalletx/connect 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BRAND.md +16 -0
- package/README.md +44 -0
- package/dist/bwalletx-shell.global.js +54 -0
- package/dist/bwalletx-shell.global.js.map +1 -0
- package/dist/chunk-OMEKHKMM.js +530 -0
- package/dist/chunk-OMEKHKMM.js.map +1 -0
- package/dist/manifest-Caj_Raqm.d.cts +81 -0
- package/dist/manifest-Caj_Raqm.d.ts +81 -0
- package/dist/shell-react.cjs +361 -0
- package/dist/shell-react.cjs.map +1 -0
- package/dist/shell-react.d.cts +58 -0
- package/dist/shell-react.d.ts +58 -0
- package/dist/shell-react.js +52 -0
- package/dist/shell-react.js.map +1 -0
- package/dist/shell.cjs +578 -0
- package/dist/shell.cjs.map +1 -0
- package/dist/shell.d.cts +132 -0
- package/dist/shell.d.ts +132 -0
- package/dist/shell.js +51 -0
- package/dist/shell.js.map +1 -0
- package/package.json +30 -4
- package/schema/bapp.schema.json +95 -0
package/dist/shell.cjs
ADDED
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/shell/index.ts
|
|
21
|
+
var shell_exports = {};
|
|
22
|
+
__export(shell_exports, {
|
|
23
|
+
BwalletxBarElement: () => BwalletxBarElement,
|
|
24
|
+
BwalletxDrawerElement: () => BwalletxDrawerElement,
|
|
25
|
+
BwalletxTopbarElement: () => BwalletxTopbarElement,
|
|
26
|
+
DEFAULT_WALLET_ORIGINS: () => DEFAULT_WALLET_ORIGINS,
|
|
27
|
+
ICONS: () => ICONS,
|
|
28
|
+
LIKE_DEFAULT_CENTS: () => LIKE_DEFAULT_CENTS,
|
|
29
|
+
LIKE_MAX_DAILY_CENTS: () => LIKE_MAX_DAILY_CENTS,
|
|
30
|
+
SLOT_IDS: () => SLOT_IDS,
|
|
31
|
+
ShellBridge: () => ShellBridge,
|
|
32
|
+
TOKENS: () => TOKENS,
|
|
33
|
+
WELL_KNOWN_PATH: () => WELL_KNOWN_PATH,
|
|
34
|
+
_resetWalletState: () => _resetWalletState,
|
|
35
|
+
applyManifest: () => applyManifest,
|
|
36
|
+
defineBappShell: () => defineBappShell,
|
|
37
|
+
detectWalletUA: () => detectWalletUA,
|
|
38
|
+
isSameOriginPath: () => isSameOriginPath,
|
|
39
|
+
isSlotId: () => isSlotId,
|
|
40
|
+
loadManifest: () => loadManifest,
|
|
41
|
+
parseManifest: () => parseManifest,
|
|
42
|
+
slotPath: () => slotPath,
|
|
43
|
+
startShellBridge: () => startShellBridge,
|
|
44
|
+
validateManifest: () => validateManifest,
|
|
45
|
+
walletState: () => walletState
|
|
46
|
+
});
|
|
47
|
+
module.exports = __toCommonJS(shell_exports);
|
|
48
|
+
|
|
49
|
+
// src/shell/manifest.ts
|
|
50
|
+
var SLOT_IDS = ["wallet", "exchange", "b", "feed", "chat"];
|
|
51
|
+
var WELL_KNOWN_PATH = "/.well-known/bapp.json";
|
|
52
|
+
var LIKE_DEFAULT_CENTS = 1;
|
|
53
|
+
var LIKE_MAX_DAILY_CENTS = 100;
|
|
54
|
+
function isSameOriginPath(p) {
|
|
55
|
+
if (typeof p !== "string" || p.length === 0 || p.length > 512) return false;
|
|
56
|
+
if (p[0] !== "/" || p[1] === "/" || p[1] === "\\") return false;
|
|
57
|
+
if (/[\\\u0000-\u001f\u007f\s]/.test(p)) return false;
|
|
58
|
+
try {
|
|
59
|
+
return new URL(p, "https://bapp.invalid").origin === "https://bapp.invalid";
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
65
|
+
var str = (v, max = 64) => typeof v === "string" && v.trim().length > 0 && v.length <= max;
|
|
66
|
+
function validateManifest(input) {
|
|
67
|
+
const errors = [];
|
|
68
|
+
if (!isObj(input)) return { ok: false, errors: ["manifest must be a JSON object"] };
|
|
69
|
+
const m = input;
|
|
70
|
+
const known = /* @__PURE__ */ new Set(["$schema", "version", "name", "icon", "home", "theme", "slots", "drawer", "like"]);
|
|
71
|
+
for (const k of Object.keys(m)) if (!known.has(k)) errors.push(`unknown field "${k}"`);
|
|
72
|
+
if (m.version !== 1) errors.push("version must be 1");
|
|
73
|
+
if (!str(m.name, 40)) errors.push("name must be a non-empty string (max 40)");
|
|
74
|
+
if (!isSameOriginPath(m.icon)) errors.push('icon must be a same-origin path starting with "/"');
|
|
75
|
+
const home = m.home === void 0 ? "/" : m.home;
|
|
76
|
+
if (!isSameOriginPath(home)) errors.push('home must be a same-origin path starting with "/"');
|
|
77
|
+
let theme;
|
|
78
|
+
if (m.theme !== void 0) {
|
|
79
|
+
if (!isObj(m.theme)) errors.push("theme must be an object");
|
|
80
|
+
else {
|
|
81
|
+
for (const k of Object.keys(m.theme)) if (k !== "accent") errors.push(`unknown field "theme.${k}"`);
|
|
82
|
+
if (m.theme.accent !== void 0 && !(typeof m.theme.accent === "string" && /^#[0-9a-fA-F]{6}$/.test(m.theme.accent)))
|
|
83
|
+
errors.push("theme.accent must be #RRGGBB");
|
|
84
|
+
theme = m.theme.accent ? { accent: m.theme.accent } : {};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const slots = {};
|
|
88
|
+
if (!isObj(m.slots)) errors.push("slots must be an object with wallet, exchange, b, feed, chat");
|
|
89
|
+
else {
|
|
90
|
+
for (const k of Object.keys(m.slots)) if (!SLOT_IDS.includes(k)) errors.push(`unknown slot "${k}"`);
|
|
91
|
+
for (const id of SLOT_IDS) {
|
|
92
|
+
const s = m.slots[id];
|
|
93
|
+
if (!isObj(s)) {
|
|
94
|
+
errors.push(`slots.${id} is required (use "enabled": false for an unused slot)`);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
for (const k of Object.keys(s)) if (!["path", "label", "enabled"].includes(k)) errors.push(`unknown field "slots.${id}.${k}"`);
|
|
98
|
+
if (typeof s.enabled !== "boolean") errors.push(`slots.${id}.enabled must be true or false`);
|
|
99
|
+
if (s.path !== void 0 && !isSameOriginPath(s.path)) errors.push(`slots.${id}.path must be a same-origin path starting with "/"`);
|
|
100
|
+
if (s.enabled === true && s.path === void 0 && id !== "b") errors.push(`slots.${id}.path is required when enabled`);
|
|
101
|
+
if (s.label !== void 0 && !str(s.label, 12)) errors.push(`slots.${id}.label must be a short string (max 12)`);
|
|
102
|
+
slots[id] = { enabled: s.enabled === true, ...s.path !== void 0 ? { path: s.path } : {}, ...s.label !== void 0 ? { label: s.label } : {} };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const drawer = [];
|
|
106
|
+
if (m.drawer !== void 0) {
|
|
107
|
+
if (!Array.isArray(m.drawer)) errors.push("drawer must be an array");
|
|
108
|
+
else if (m.drawer.length > 24) errors.push("drawer has more than 24 items");
|
|
109
|
+
else m.drawer.forEach((d, i) => {
|
|
110
|
+
if (!isObj(d)) {
|
|
111
|
+
errors.push(`drawer[${i}] must be an object`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
for (const k of Object.keys(d)) if (!["label", "path", "icon"].includes(k)) errors.push(`unknown field "drawer[${i}].${k}"`);
|
|
115
|
+
if (!str(d.label, 40)) errors.push(`drawer[${i}].label must be a non-empty string (max 40)`);
|
|
116
|
+
if (!isSameOriginPath(d.path)) errors.push(`drawer[${i}].path must be a same-origin path starting with "/"`);
|
|
117
|
+
if (d.icon !== void 0 && !isSameOriginPath(d.icon)) errors.push(`drawer[${i}].icon must be a same-origin path starting with "/"`);
|
|
118
|
+
drawer.push({ label: d.label, path: d.path, ...d.icon ? { icon: d.icon } : {} });
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
let like;
|
|
122
|
+
if (m.like !== void 0) {
|
|
123
|
+
const l = m.like;
|
|
124
|
+
if (!isObj(l)) errors.push("like must be an object");
|
|
125
|
+
else {
|
|
126
|
+
for (const k of Object.keys(l)) if (!["fields", "amountCents", "currency", "dailyCapCents"].includes(k)) errors.push(`unknown field "like.${k}"`);
|
|
127
|
+
const f = l.fields;
|
|
128
|
+
if (!isObj(f) || !str(f.id) || !str(f.token)) errors.push('like.fields needs "id" and "token" field names');
|
|
129
|
+
else for (const k of ["payee", "title", "media"]) if (f[k] !== void 0 && !str(f[k])) errors.push(`like.fields.${k} must be a field name`);
|
|
130
|
+
const amount = l.amountCents ?? LIKE_DEFAULT_CENTS;
|
|
131
|
+
const cap = l.dailyCapCents ?? LIKE_MAX_DAILY_CENTS;
|
|
132
|
+
const currency = l.currency ?? "PNEE";
|
|
133
|
+
if (!Number.isInteger(amount) || amount < 1) errors.push("like.amountCents must be a whole number of cents, at least 1");
|
|
134
|
+
if (!Number.isInteger(cap) || cap < 1 || cap > LIKE_MAX_DAILY_CENTS) errors.push("like.dailyCapCents must be 1..100 ($1 max)");
|
|
135
|
+
else if (Number.isInteger(amount) && amount > cap) errors.push("like.amountCents cannot exceed like.dailyCapCents");
|
|
136
|
+
if (currency !== "PNEE" && currency !== "BSV") errors.push('like.currency must be "PNEE" or "BSV"');
|
|
137
|
+
if (isObj(f)) {
|
|
138
|
+
const fields = { id: f.id, token: f.token };
|
|
139
|
+
for (const k of ["payee", "title", "media"]) if (typeof f[k] === "string") fields[k] = f[k];
|
|
140
|
+
like = { fields, amountCents: amount, currency, dailyCapCents: cap };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (errors.length) return { ok: false, errors };
|
|
145
|
+
return {
|
|
146
|
+
ok: true,
|
|
147
|
+
errors: [],
|
|
148
|
+
manifest: {
|
|
149
|
+
version: 1,
|
|
150
|
+
name: m.name.trim(),
|
|
151
|
+
icon: m.icon,
|
|
152
|
+
home,
|
|
153
|
+
...theme ? { theme } : {},
|
|
154
|
+
slots,
|
|
155
|
+
drawer,
|
|
156
|
+
...like ? { like } : {}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function parseManifest(input) {
|
|
161
|
+
const r = validateManifest(input);
|
|
162
|
+
if (!r.ok) throw new Error(`Invalid bapp.json:
|
|
163
|
+
- ${r.errors.join("\n- ")}`);
|
|
164
|
+
return r.manifest;
|
|
165
|
+
}
|
|
166
|
+
async function loadManifest(path = WELL_KNOWN_PATH, fetchImpl = fetch) {
|
|
167
|
+
if (!isSameOriginPath(path)) throw new Error("loadManifest: path must be same-origin");
|
|
168
|
+
const res = await fetchImpl(path, { credentials: "same-origin", headers: { accept: "application/json" } });
|
|
169
|
+
if (!res.ok) throw new Error(`loadManifest: ${path} returned ${res.status}`);
|
|
170
|
+
return parseManifest(await res.json());
|
|
171
|
+
}
|
|
172
|
+
function slotPath(m, slot) {
|
|
173
|
+
const s = m.slots[slot];
|
|
174
|
+
return s && s.enabled && s.path ? s.path : null;
|
|
175
|
+
}
|
|
176
|
+
var isSlotId = (v) => typeof v === "string" && SLOT_IDS.includes(v);
|
|
177
|
+
|
|
178
|
+
// src/shell/wallet.ts
|
|
179
|
+
var DEFAULT_WALLET_ORIGINS = ["https://web.bwalletx.com", "https://bwalletx.com"];
|
|
180
|
+
var UA_RE = /\b(?:bWallet|YoursWalletMobile)\//;
|
|
181
|
+
var INSET_RE = /\bbWalletInset\/(\d{1,4})\b/;
|
|
182
|
+
function detectWalletUA(ua = typeof navigator !== "undefined" ? navigator.userAgent : "") {
|
|
183
|
+
const inset = INSET_RE.exec(ua);
|
|
184
|
+
if (inset || UA_RE.test(ua)) return { inWallet: true, via: "ua", ...inset ? { inset: Number(inset[1]) } : {} };
|
|
185
|
+
return { inWallet: false, via: null };
|
|
186
|
+
}
|
|
187
|
+
var state = { inWallet: false, via: null };
|
|
188
|
+
function walletState() {
|
|
189
|
+
return state;
|
|
190
|
+
}
|
|
191
|
+
function setState(next, win) {
|
|
192
|
+
state = next;
|
|
193
|
+
if (next.inWallet) win.document?.documentElement?.setAttribute("data-bwx-in-wallet", next.via ?? "");
|
|
194
|
+
win.dispatchEvent(new CustomEvent("bapp-wallet", { detail: next }));
|
|
195
|
+
}
|
|
196
|
+
var ShellBridge = class {
|
|
197
|
+
manifest;
|
|
198
|
+
origins;
|
|
199
|
+
win;
|
|
200
|
+
target = null;
|
|
201
|
+
active = null;
|
|
202
|
+
onMessage = (e) => this.handle(e);
|
|
203
|
+
onActive = (e) => {
|
|
204
|
+
const s = e.detail?.slot;
|
|
205
|
+
if (isSlotId(s)) this.setActive(s);
|
|
206
|
+
};
|
|
207
|
+
onMenu = () => this.post({ type: "bapp:menu" });
|
|
208
|
+
onClose = () => this.post({ type: "bapp:close" });
|
|
209
|
+
constructor(opts) {
|
|
210
|
+
this.manifest = opts.manifest;
|
|
211
|
+
this.origins = opts.walletOrigins ?? DEFAULT_WALLET_ORIGINS;
|
|
212
|
+
this.win = opts.win ?? window;
|
|
213
|
+
const ua = detectWalletUA(opts.userAgent);
|
|
214
|
+
if (ua.inWallet) {
|
|
215
|
+
this.target = { win: this.win, origin: this.win.location.origin };
|
|
216
|
+
setState(ua, this.win);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
start() {
|
|
220
|
+
this.win.addEventListener("message", this.onMessage);
|
|
221
|
+
this.win.addEventListener("bapp-active", this.onActive);
|
|
222
|
+
this.win.addEventListener("bapp-menu", this.onMenu);
|
|
223
|
+
this.win.addEventListener("bapp-close", this.onClose);
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
stop() {
|
|
227
|
+
this.win.removeEventListener("message", this.onMessage);
|
|
228
|
+
this.win.removeEventListener("bapp-active", this.onActive);
|
|
229
|
+
this.win.removeEventListener("bapp-menu", this.onMenu);
|
|
230
|
+
this.win.removeEventListener("bapp-close", this.onClose);
|
|
231
|
+
}
|
|
232
|
+
get inWallet() {
|
|
233
|
+
return this.target !== null;
|
|
234
|
+
}
|
|
235
|
+
/** Tell the wallet which slot is showing (lights up the native dock). */
|
|
236
|
+
setActive(slot) {
|
|
237
|
+
this.active = slot;
|
|
238
|
+
this.post({ type: "bapp:active", v: 1, slot });
|
|
239
|
+
}
|
|
240
|
+
/** Is this message from a trusted wallet? */
|
|
241
|
+
trusted(e) {
|
|
242
|
+
const parent = this.win.parent;
|
|
243
|
+
if (parent && parent !== this.win && e.source === parent) return this.origins.includes(e.origin);
|
|
244
|
+
if (this.target?.win === this.win && (e.source === this.win || e.source === null)) return e.origin === this.win.location.origin;
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
handle(e) {
|
|
248
|
+
const d = e.data;
|
|
249
|
+
if (!d || typeof d !== "object" || typeof d.type !== "string" || !d.type.startsWith("bapp:")) return;
|
|
250
|
+
if (!this.trusted(e)) return;
|
|
251
|
+
if (d.type === "bapp:hello") {
|
|
252
|
+
if (!this.target) {
|
|
253
|
+
this.target = { win: e.source, origin: e.origin };
|
|
254
|
+
setState({ inWallet: true, via: "frame" }, this.win);
|
|
255
|
+
}
|
|
256
|
+
this.post({ type: "bapp:ready", v: 1, manifest: "/.well-known/bapp.json", ...this.active ? { slot: this.active } : {} });
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (d.type === "bapp:navigate") {
|
|
260
|
+
if (!isSlotId(d.slot)) return;
|
|
261
|
+
const path = slotPath(this.manifest, d.slot);
|
|
262
|
+
if (d.slot !== "b" && !path) return;
|
|
263
|
+
if (d.slot === "b" && !this.manifest.slots.b.enabled) return;
|
|
264
|
+
const ev = new CustomEvent("bapp-navigate", { cancelable: true, detail: { slot: d.slot, path, source: "wallet" } });
|
|
265
|
+
this.win.dispatchEvent(ev);
|
|
266
|
+
if (!ev.defaultPrevented && path) this.win.location.assign(path);
|
|
267
|
+
this.setActive(d.slot);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
post(msg) {
|
|
271
|
+
if (!this.target) return;
|
|
272
|
+
const { win, origin } = this.target;
|
|
273
|
+
const rn = this.win.ReactNativeWebView;
|
|
274
|
+
if (win === this.win && rn) rn.postMessage(JSON.stringify(msg));
|
|
275
|
+
else win.postMessage(msg, origin);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
function startShellBridge(opts) {
|
|
279
|
+
return new ShellBridge(opts).start();
|
|
280
|
+
}
|
|
281
|
+
function _resetWalletState() {
|
|
282
|
+
state = { inWallet: false, via: null };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// src/shell/elements.ts
|
|
286
|
+
var TOKENS = {
|
|
287
|
+
barHeight: 88,
|
|
288
|
+
barBg: "#0A0B0D",
|
|
289
|
+
hairline: "#1C1C1E",
|
|
290
|
+
icon: 20,
|
|
291
|
+
active: "#FFD24D",
|
|
292
|
+
inactive: "#F2F2F0",
|
|
293
|
+
label: "#98A2B3",
|
|
294
|
+
bSize: 52,
|
|
295
|
+
stripHeight: 28,
|
|
296
|
+
rowHeight: 56,
|
|
297
|
+
tool: 36,
|
|
298
|
+
toolRing: "#2A2A2C",
|
|
299
|
+
toolIcon: "#F5B800"
|
|
300
|
+
};
|
|
301
|
+
var DEFAULT_LABELS = { wallet: "Wallet", exchange: "Exchange", b: "b", feed: "Feed", chat: "Chat" };
|
|
302
|
+
var svg = (d) => `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${d}</svg>`;
|
|
303
|
+
var ICONS = {
|
|
304
|
+
wallet: svg('<rect x="3" y="6" width="18" height="13" rx="2"/><path d="M3 10h18"/>'),
|
|
305
|
+
exchange: svg('<path d="M7 4v16M3 8l4-4 4 4M17 20V4M13 16l4 4 4-4"/>'),
|
|
306
|
+
feed: svg('<rect x="4" y="4" width="16" height="16" rx="2"/><path d="M8 9h8M8 13h8M8 17h5"/>'),
|
|
307
|
+
chat: svg('<path d="M21 12a8 8 0 0 1-12 7l-5 1 1-4a8 8 0 1 1 16-4z"/>'),
|
|
308
|
+
menu: svg('<path d="M4 6h16M4 12h16M4 18h16"/>'),
|
|
309
|
+
close: svg('<path d="M6 6l12 12M18 6L6 18"/>')
|
|
310
|
+
};
|
|
311
|
+
var esc = (s) => s.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
312
|
+
var Base = typeof HTMLElement === "undefined" ? class {
|
|
313
|
+
} : HTMLElement;
|
|
314
|
+
function inWallet(el) {
|
|
315
|
+
const a = el.getAttribute("in-wallet");
|
|
316
|
+
if (a === "true" || a === "") return true;
|
|
317
|
+
if (a === "false") return false;
|
|
318
|
+
return walletState().inWallet || detectWalletUA().inWallet;
|
|
319
|
+
}
|
|
320
|
+
function emit(el, type, detail, cancelable = false) {
|
|
321
|
+
return el.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable }));
|
|
322
|
+
}
|
|
323
|
+
function navigate(el, detail) {
|
|
324
|
+
const ok = emit(el, "bapp-navigate", { ...detail, source: "web" }, true);
|
|
325
|
+
if (ok && detail.path && el.getAttribute("navigate") !== "manual") location.assign(detail.path);
|
|
326
|
+
}
|
|
327
|
+
var ShellElement = class extends Base {
|
|
328
|
+
_manifest = null;
|
|
329
|
+
onWallet = () => this.render();
|
|
330
|
+
get manifest() {
|
|
331
|
+
return this._manifest;
|
|
332
|
+
}
|
|
333
|
+
set manifest(m) {
|
|
334
|
+
this._manifest = m;
|
|
335
|
+
this.render();
|
|
336
|
+
}
|
|
337
|
+
connectedCallback() {
|
|
338
|
+
if (!this.shadowRoot) this.attachShadow({ mode: "open" });
|
|
339
|
+
window.addEventListener("bapp-wallet", this.onWallet);
|
|
340
|
+
this.render();
|
|
341
|
+
}
|
|
342
|
+
disconnectedCallback() {
|
|
343
|
+
window.removeEventListener("bapp-wallet", this.onWallet);
|
|
344
|
+
}
|
|
345
|
+
attributeChangedCallback() {
|
|
346
|
+
if (this.shadowRoot) this.render();
|
|
347
|
+
}
|
|
348
|
+
accent() {
|
|
349
|
+
return this._manifest?.theme?.accent ?? TOKENS.active;
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
var BAR_CSS = `
|
|
353
|
+
:host{display:block;--bwx-accent:${TOKENS.active}}
|
|
354
|
+
:host([hidden]),:host([data-in-wallet]){display:none!important}
|
|
355
|
+
nav{height:${TOKENS.barHeight}px;box-sizing:border-box;background:${TOKENS.barBg};border-top:1px solid ${TOKENS.hairline};
|
|
356
|
+
display:flex;align-items:flex-start;padding:10px 0 env(safe-area-inset-bottom,0);font:10px/1.2 -apple-system,BlinkMacSystemFont,"SF Pro Text",system-ui,sans-serif}
|
|
357
|
+
button{all:unset;box-sizing:border-box;flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;gap:4px;cursor:pointer;
|
|
358
|
+
color:${TOKENS.inactive};-webkit-tap-highlight-color:transparent;touch-action:manipulation}
|
|
359
|
+
button:focus-visible{outline:2px solid var(--bwx-accent);outline-offset:2px;border-radius:10px}
|
|
360
|
+
button svg{width:${TOKENS.icon}px;height:${TOKENS.icon}px}
|
|
361
|
+
.lbl{font-size:10px;color:${TOKENS.label};white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
|
|
362
|
+
button[aria-current="page"]{color:var(--bwx-accent)}
|
|
363
|
+
button[aria-current="page"] .lbl{color:var(--bwx-accent);font-weight:700}
|
|
364
|
+
button:disabled{opacity:.35;cursor:default;pointer-events:none}
|
|
365
|
+
.b{width:${TOKENS.bSize}px;height:${TOKENS.bSize}px;border-radius:50%;background:#000;border:2px solid var(--bwx-accent);box-sizing:border-box;
|
|
366
|
+
display:grid;place-items:center;font:800 24px/1 Georgia,"Times New Roman",serif;color:var(--bwx-accent);margin-top:-4px}
|
|
367
|
+
button[data-slot="b"][aria-current="page"] .b{box-shadow:0 0 0 3px #FFD24D33}
|
|
368
|
+
`;
|
|
369
|
+
var BwalletxBarElement = class extends ShellElement {
|
|
370
|
+
static observedAttributes = ["active", "in-wallet", "hidden"];
|
|
371
|
+
holdTimer = null;
|
|
372
|
+
held = false;
|
|
373
|
+
get active() {
|
|
374
|
+
const a = this.getAttribute("active");
|
|
375
|
+
return isSlotId(a) ? a : null;
|
|
376
|
+
}
|
|
377
|
+
set active(s) {
|
|
378
|
+
if (s) this.setAttribute("active", s);
|
|
379
|
+
else this.removeAttribute("active");
|
|
380
|
+
}
|
|
381
|
+
attributeChangedCallback(name, old, val) {
|
|
382
|
+
super.attributeChangedCallback();
|
|
383
|
+
if (name === "active" && val !== old && isSlotId(val)) emit(this, "bapp-active", { slot: val });
|
|
384
|
+
}
|
|
385
|
+
render() {
|
|
386
|
+
const root = this.shadowRoot;
|
|
387
|
+
if (!root) return;
|
|
388
|
+
if (inWallet(this)) this.setAttribute("data-in-wallet", "");
|
|
389
|
+
else this.removeAttribute("data-in-wallet");
|
|
390
|
+
const m = this._manifest;
|
|
391
|
+
const active = this.active;
|
|
392
|
+
const slots = SLOT_IDS.map((id) => {
|
|
393
|
+
const s = m?.slots[id];
|
|
394
|
+
const enabled = !!s?.enabled;
|
|
395
|
+
const label = s?.label ?? DEFAULT_LABELS[id];
|
|
396
|
+
const cur = active === id ? ' aria-current="page"' : "";
|
|
397
|
+
const dis = enabled ? "" : ' disabled aria-disabled="true"';
|
|
398
|
+
if (id === "b") return `<button type="button" data-slot="b" aria-label="b agent${m ? ` in ${esc(m.name)}` : ""}"${cur}${dis}><span class="b">b</span></button>`;
|
|
399
|
+
return `<button type="button" data-slot="${id}" aria-label="${esc(label)}"${cur}${dis}>${ICONS[id]}<span class="lbl">${esc(label)}</span></button>`;
|
|
400
|
+
}).join("");
|
|
401
|
+
root.innerHTML = `<style>${BAR_CSS}</style><nav part="bar" aria-label="${m ? esc(m.name) : "App"} navigation" style="--bwx-accent:${this.accent()}">${slots}</nav>`;
|
|
402
|
+
root.querySelectorAll("button[data-slot]").forEach((b) => {
|
|
403
|
+
const id = b.dataset.slot;
|
|
404
|
+
if (id === "b") this.wireB(b);
|
|
405
|
+
else b.addEventListener("click", () => this.go(id));
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
go(id) {
|
|
409
|
+
const m = this._manifest;
|
|
410
|
+
if (!m || !m.slots[id]?.enabled) return;
|
|
411
|
+
this.active = id;
|
|
412
|
+
navigate(this, { slot: id, path: slotPath(m, id) });
|
|
413
|
+
}
|
|
414
|
+
/** (b): tap = bapp-b-press, hold (>=450ms) = bapp-b-hold {phase:'start'|'end'}. Hold-to-talk is a later phase. */
|
|
415
|
+
wireB(b) {
|
|
416
|
+
const clear = () => {
|
|
417
|
+
if (this.holdTimer) clearTimeout(this.holdTimer);
|
|
418
|
+
this.holdTimer = null;
|
|
419
|
+
};
|
|
420
|
+
b.addEventListener("pointerdown", () => {
|
|
421
|
+
this.held = false;
|
|
422
|
+
clear();
|
|
423
|
+
this.holdTimer = setTimeout(() => {
|
|
424
|
+
this.held = true;
|
|
425
|
+
emit(this, "bapp-b-hold", { phase: "start" });
|
|
426
|
+
}, 450);
|
|
427
|
+
});
|
|
428
|
+
const up = () => {
|
|
429
|
+
clear();
|
|
430
|
+
if (this.held) emit(this, "bapp-b-hold", { phase: "end" });
|
|
431
|
+
};
|
|
432
|
+
b.addEventListener("pointerup", up);
|
|
433
|
+
b.addEventListener("pointercancel", up);
|
|
434
|
+
b.addEventListener("pointerleave", up);
|
|
435
|
+
b.addEventListener("click", () => {
|
|
436
|
+
if (this.held) {
|
|
437
|
+
this.held = false;
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
if (!this._manifest?.slots.b.enabled) return;
|
|
441
|
+
emit(this, "bapp-b-press", { path: slotPath(this._manifest, "b") });
|
|
442
|
+
const p = slotPath(this._manifest, "b");
|
|
443
|
+
if (p) this.go("b");
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
var TOP_CSS = `
|
|
448
|
+
:host{display:block;--bwx-accent:${TOKENS.active};font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text",system-ui,sans-serif}
|
|
449
|
+
:host([hidden]){display:none!important}
|
|
450
|
+
.strip{height:${TOKENS.stripHeight}px;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font-size:11px;color:#9a9a9a;background:${TOKENS.barBg}}
|
|
451
|
+
::slotted([slot^="strip"]){color:inherit}
|
|
452
|
+
.row{height:${TOKENS.rowHeight}px;box-sizing:border-box;display:flex;align-items:center;gap:8px;padding:0 12px;background:${TOKENS.barBg};border-bottom:1px solid ${TOKENS.hairline}}
|
|
453
|
+
.btn,::slotted([slot="tools"]){all:unset;box-sizing:border-box;width:${TOKENS.tool}px;height:${TOKENS.tool}px;flex:none;border-radius:50%;border:1px solid ${TOKENS.toolRing};
|
|
454
|
+
display:grid;place-items:center;color:${TOKENS.toolIcon};cursor:pointer;-webkit-tap-highlight-color:transparent}
|
|
455
|
+
.btn:focus-visible{outline:2px solid var(--bwx-accent);outline-offset:2px}
|
|
456
|
+
.btn svg{width:18px;height:18px}
|
|
457
|
+
.app{border-color:${TOKENS.toolIcon};background:#1a1405;font-weight:800;font-size:13px;line-height:1;overflow:hidden}
|
|
458
|
+
.app img{width:100%;height:100%;object-fit:cover}
|
|
459
|
+
.title{flex:1;min-width:0;color:#F2F2F0;font-weight:700;font-size:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
460
|
+
.title small{display:block;color:#777;font-weight:500;font-size:11px}
|
|
461
|
+
.tools{display:flex;gap:8px}
|
|
462
|
+
`;
|
|
463
|
+
var BwalletxTopbarElement = class extends ShellElement {
|
|
464
|
+
static observedAttributes = ["title", "subtitle", "in-wallet", "drawer", "menu", "strip", "hidden"];
|
|
465
|
+
render() {
|
|
466
|
+
const root = this.shadowRoot;
|
|
467
|
+
if (!root) return;
|
|
468
|
+
const m = this._manifest;
|
|
469
|
+
const wallet = inWallet(this);
|
|
470
|
+
const menu = wallet || this.getAttribute("menu") === "true";
|
|
471
|
+
const name = this.getAttribute("title") ?? m?.name ?? "";
|
|
472
|
+
const sub = this.getAttribute("subtitle");
|
|
473
|
+
const initials = name.replace(/[^A-Za-z0-9]/g, "").slice(0, 2) || "b";
|
|
474
|
+
const icon = m?.icon ? `<img src="${esc(m.icon)}" alt="">` : esc(initials);
|
|
475
|
+
const strip = this.getAttribute("strip") !== "off";
|
|
476
|
+
root.innerHTML = `<style>${TOP_CSS}</style><header part="topbar" style="--bwx-accent:${this.accent()}">
|
|
477
|
+
${strip ? `<div class="strip" part="strip"><slot name="strip-left"></slot><slot name="strip-right"></slot></div>` : ""}
|
|
478
|
+
<div class="row" part="row">
|
|
479
|
+
${menu ? `<button type="button" class="btn" data-act="menu" aria-label="bWalletX menu">${ICONS.menu}</button>` : ""}
|
|
480
|
+
<button type="button" class="btn app" data-act="drawer" aria-label="${esc(name || "App")} home and sections" aria-haspopup="true">${icon}</button>
|
|
481
|
+
<div class="title" part="title">${esc(name)}${sub ? `<small>${esc(sub)}</small>` : ""}</div>
|
|
482
|
+
<div class="tools"><slot name="tools"></slot>${wallet ? `<button type="button" class="btn" data-act="close" aria-label="Close ${esc(name || "app")}">${ICONS.close}</button>` : ""}</div>
|
|
483
|
+
</div></header>`;
|
|
484
|
+
root.querySelector('[data-act="menu"]')?.addEventListener("click", () => emit(this, "bapp-menu", {}));
|
|
485
|
+
root.querySelector('[data-act="close"]')?.addEventListener("click", () => emit(this, "bapp-close", {}));
|
|
486
|
+
root.querySelector('[data-act="drawer"]')?.addEventListener("click", () => {
|
|
487
|
+
if (!emit(this, "bapp-drawer-toggle", {}, true)) return;
|
|
488
|
+
const id = this.getAttribute("drawer");
|
|
489
|
+
const d = id ? document.getElementById(id) : document.querySelector("bwalletx-drawer");
|
|
490
|
+
d?.toggle();
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
var DRAWER_CSS = `
|
|
495
|
+
:host{position:fixed;inset:var(--bwx-drawer-top,84px) 0 var(--bwx-drawer-bottom,88px) 0;z-index:50;display:none;font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text",system-ui,sans-serif}
|
|
496
|
+
:host([open]){display:block}
|
|
497
|
+
.scrim{position:absolute;inset:0;background:#0009}
|
|
498
|
+
nav{position:absolute;top:0;left:0;bottom:0;width:min(78%,320px);box-sizing:border-box;background:#0d0e10;border-right:1px solid ${TOKENS.hairline};padding:14px;overflow:auto}
|
|
499
|
+
h4{margin:10px 0 6px;font-size:11px;color:#777;letter-spacing:.08em;text-transform:uppercase;font-weight:600}
|
|
500
|
+
a{display:flex;align-items:center;gap:10px;padding:11px 10px;border-radius:10px;color:#F2F2F0;text-decoration:none;font-size:15px}
|
|
501
|
+
a img{width:20px;height:20px}
|
|
502
|
+
a[aria-current="page"]{background:#1a1405;color:var(--bwx-accent)}
|
|
503
|
+
a:focus-visible{outline:2px solid var(--bwx-accent)}
|
|
504
|
+
hr{border:0;border-top:1px solid ${TOKENS.hairline};margin:10px 0}
|
|
505
|
+
.back{color:${TOKENS.toolIcon}}
|
|
506
|
+
`;
|
|
507
|
+
var BwalletxDrawerElement = class extends ShellElement {
|
|
508
|
+
static observedAttributes = ["open", "current", "in-wallet"];
|
|
509
|
+
get open() {
|
|
510
|
+
return this.hasAttribute("open");
|
|
511
|
+
}
|
|
512
|
+
set open(v) {
|
|
513
|
+
this.toggleAttribute("open", v);
|
|
514
|
+
}
|
|
515
|
+
toggle(force) {
|
|
516
|
+
this.open = force ?? !this.open;
|
|
517
|
+
}
|
|
518
|
+
render() {
|
|
519
|
+
const root = this.shadowRoot;
|
|
520
|
+
if (!root) return;
|
|
521
|
+
const m = this._manifest;
|
|
522
|
+
const cur = this.getAttribute("current") ?? (typeof location !== "undefined" ? location.pathname : "/");
|
|
523
|
+
const items = m ? [{ label: "Home", path: m.home }, ...m.drawer] : [];
|
|
524
|
+
const links = items.map((it, i) => `<a href="${esc(it.path)}" data-i="${i}"${it.path === cur ? ' aria-current="page"' : ""}>${"icon" in it && it.icon ? `<img src="${esc(it.icon)}" alt="">` : ""}${esc(it.label)}</a>`).join("");
|
|
525
|
+
const back = inWallet(this) ? `<hr><a href="#" class="back" data-act="close">\u2190 Back to bWalletX</a>` : "";
|
|
526
|
+
root.innerHTML = `<style>${DRAWER_CSS}</style><div class="scrim" part="scrim"></div><nav part="drawer" aria-label="${m ? esc(m.name) : "App"} sections" style="--bwx-accent:${this.accent()}"><h4>${m ? esc(m.name) : ""}</h4>${links}${back}</nav>`;
|
|
527
|
+
root.querySelector(".scrim").addEventListener("click", () => this.toggle(false));
|
|
528
|
+
root.querySelectorAll("a[data-i]").forEach((a) => a.addEventListener("click", (e) => {
|
|
529
|
+
e.preventDefault();
|
|
530
|
+
const it = items[Number(a.dataset.i)];
|
|
531
|
+
this.toggle(false);
|
|
532
|
+
navigate(this, { path: it.path });
|
|
533
|
+
}));
|
|
534
|
+
root.querySelector('[data-act="close"]')?.addEventListener("click", (e) => {
|
|
535
|
+
e.preventDefault();
|
|
536
|
+
this.toggle(false);
|
|
537
|
+
emit(this, "bapp-close", {});
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
function defineBappShell() {
|
|
542
|
+
if (typeof customElements === "undefined") return;
|
|
543
|
+
if (!customElements.get("bwalletx-bar")) customElements.define("bwalletx-bar", BwalletxBarElement);
|
|
544
|
+
if (!customElements.get("bwalletx-topbar")) customElements.define("bwalletx-topbar", BwalletxTopbarElement);
|
|
545
|
+
if (!customElements.get("bwalletx-drawer")) customElements.define("bwalletx-drawer", BwalletxDrawerElement);
|
|
546
|
+
}
|
|
547
|
+
function applyManifest(m, root = document) {
|
|
548
|
+
root.querySelectorAll("bwalletx-bar,bwalletx-topbar,bwalletx-drawer").forEach((el) => {
|
|
549
|
+
el.manifest = m;
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
553
|
+
0 && (module.exports = {
|
|
554
|
+
BwalletxBarElement,
|
|
555
|
+
BwalletxDrawerElement,
|
|
556
|
+
BwalletxTopbarElement,
|
|
557
|
+
DEFAULT_WALLET_ORIGINS,
|
|
558
|
+
ICONS,
|
|
559
|
+
LIKE_DEFAULT_CENTS,
|
|
560
|
+
LIKE_MAX_DAILY_CENTS,
|
|
561
|
+
SLOT_IDS,
|
|
562
|
+
ShellBridge,
|
|
563
|
+
TOKENS,
|
|
564
|
+
WELL_KNOWN_PATH,
|
|
565
|
+
_resetWalletState,
|
|
566
|
+
applyManifest,
|
|
567
|
+
defineBappShell,
|
|
568
|
+
detectWalletUA,
|
|
569
|
+
isSameOriginPath,
|
|
570
|
+
isSlotId,
|
|
571
|
+
loadManifest,
|
|
572
|
+
parseManifest,
|
|
573
|
+
slotPath,
|
|
574
|
+
startShellBridge,
|
|
575
|
+
validateManifest,
|
|
576
|
+
walletState
|
|
577
|
+
});
|
|
578
|
+
//# sourceMappingURL=shell.cjs.map
|