@jetezra/bridge 0.0.1 → 0.0.2

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.
@@ -12,6 +12,8 @@ var BRIDGE = {
12
12
  CALL_RESULT: "chooga.callResult",
13
13
  EVENT: "chooga.event",
14
14
  HOST_INFO: "chooga.hostInfo",
15
+ /** Open bag of host-defined fields for getState() / subscribe. */
16
+ PARAMS: "chooga.params",
15
17
  // Mini-app → host
16
18
  READY: "chooga.ready",
17
19
  CLOSE: "chooga.close",
@@ -27,7 +29,22 @@ var BRIDGE = {
27
29
  HOST_INFO_GET: "chooga.hostInfo.get",
28
30
  CALL: "chooga.call"
29
31
  };
30
- var BRIDGE_VERSION = "1.2.0";
32
+ var BRIDGE_VERSION = "0.0.2";
33
+ var RESERVED_STATE_KEYS = /* @__PURE__ */ new Set([
34
+ "hostConnected",
35
+ "readySent",
36
+ "session",
37
+ "user",
38
+ "theme",
39
+ "safeArea",
40
+ "hostInfo",
41
+ "params",
42
+ "lastCapability",
43
+ "lastPayment",
44
+ "lastActivity",
45
+ "lastEvent",
46
+ "lastMessage"
47
+ ]);
31
48
  var state = {
32
49
  hostConnected: false,
33
50
  readySent: false,
@@ -36,6 +53,8 @@ var state = {
36
53
  theme: null,
37
54
  safeArea: null,
38
55
  hostInfo: null,
56
+ /** Host-defined open bag — any keys the host wants the mini-app to see. */
57
+ params: {},
39
58
  lastCapability: null,
40
59
  lastPayment: null,
41
60
  lastActivity: null,
@@ -47,12 +66,52 @@ var eventHandlers = /* @__PURE__ */ new Map();
47
66
  var pending = /* @__PURE__ */ new Map();
48
67
  var requestSeq = 0;
49
68
  var initialized = false;
69
+ var debugEnabled = false;
70
+ var allowedOrigins = null;
71
+ var mockHostActive = false;
50
72
  function nextRequestId() {
51
73
  requestSeq += 1;
52
74
  return `cb_${Date.now().toString(36)}_${requestSeq}`;
53
75
  }
76
+ function logDebug(direction, payload) {
77
+ if (!debugEnabled) {
78
+ return;
79
+ }
80
+ const label = direction === "out" ? "[ChoogaBridge \u2192 host]" : "[ChoogaBridge \u2190 host]";
81
+ try {
82
+ console.log(label, payload);
83
+ } catch (_) {
84
+ }
85
+ }
86
+ function logWarn(message, detail) {
87
+ if (!debugEnabled) {
88
+ return;
89
+ }
90
+ try {
91
+ console.warn("[ChoogaBridge]", message, detail);
92
+ } catch (_) {
93
+ }
94
+ }
95
+ function applyHostParams(msg) {
96
+ const { type: _t, replace, params: nested, ...rest } = msg || {};
97
+ const incoming = nested && typeof nested === "object" && !Array.isArray(nested) ? { ...nested } : { ...rest };
98
+ if (replace) {
99
+ state.params = incoming;
100
+ return;
101
+ }
102
+ const next = { ...state.params };
103
+ Object.keys(incoming).forEach((key) => {
104
+ if (incoming[key] === null) {
105
+ delete next[key];
106
+ } else {
107
+ next[key] = incoming[key];
108
+ }
109
+ });
110
+ state.params = next;
111
+ }
54
112
  function getState() {
55
- return {
113
+ const params = { ...state.params };
114
+ const known = {
56
115
  hostConnected: state.hostConnected,
57
116
  readySent: state.readySent,
58
117
  session: state.session,
@@ -60,12 +119,20 @@ function getState() {
60
119
  theme: state.theme,
61
120
  safeArea: state.safeArea,
62
121
  hostInfo: state.hostInfo,
122
+ params,
63
123
  lastCapability: state.lastCapability,
64
124
  lastPayment: state.lastPayment,
65
125
  lastActivity: state.lastActivity,
66
126
  lastEvent: state.lastEvent,
67
127
  lastMessage: state.lastMessage
68
128
  };
129
+ const extras = {};
130
+ Object.keys(params).forEach((key) => {
131
+ if (!RESERVED_STATE_KEYS.has(key)) {
132
+ extras[key] = params[key];
133
+ }
134
+ });
135
+ return { ...extras, ...known };
69
136
  }
70
137
  function notify() {
71
138
  const snapshot = getState();
@@ -98,6 +165,9 @@ function emitLocalEvent(name, detail) {
98
165
  }
99
166
  }
100
167
  function detectHost() {
168
+ if (mockHostActive) {
169
+ return true;
170
+ }
101
171
  return !!(typeof window !== "undefined" && window.ReactNativeWebView && typeof window.ReactNativeWebView.postMessage === "function");
102
172
  }
103
173
  function ensureChoogaHostShim() {
@@ -123,6 +193,7 @@ function ensureChoogaHostShim() {
123
193
  window.ChoogaTheme = window.ChoogaTheme || {};
124
194
  }
125
195
  function postToHost(msg) {
196
+ logDebug("out", msg);
126
197
  ensureChoogaHostShim();
127
198
  if (window.ChoogaHost && typeof window.ChoogaHost.postToHost === "function") {
128
199
  window.ChoogaHost.postToHost(msg);
@@ -176,6 +247,7 @@ function trackRequest(options = {}) {
176
247
  const timer = timeoutMs > 0 ? setTimeout(() => {
177
248
  if (pending.has(requestId)) {
178
249
  pending.delete(requestId);
250
+ logWarn("request timed out", { requestId, timeoutMs });
179
251
  reject(Object.assign(new Error("timeout"), { reason: "timeout" }));
180
252
  }
181
253
  }, timeoutMs) : null;
@@ -193,46 +265,76 @@ function handleHostMessage(raw) {
193
265
  try {
194
266
  msg = JSON.parse(raw);
195
267
  } catch (_) {
268
+ logWarn("ignored non-JSON host message", raw);
196
269
  return;
197
270
  }
198
271
  }
199
272
  if (!msg || typeof msg !== "object" || !msg.type) {
200
273
  return;
201
274
  }
275
+ logDebug("in", msg);
202
276
  state.hostConnected = true;
203
277
  state.lastMessage = msg;
204
278
  switch (msg.type) {
205
- case BRIDGE.SESSION:
279
+ case BRIDGE.SESSION: {
280
+ const { type: _t, ...rest } = msg;
206
281
  state.session = {
207
- token: msg.token || null,
208
- expires_at: msg.expires_at || null
282
+ ...rest,
283
+ token: rest.token != null ? rest.token : null,
284
+ expires_at: rest.expires_at != null ? rest.expires_at : null
209
285
  };
210
286
  break;
287
+ }
211
288
  case BRIDGE.USER:
212
- state.user = msg.user || null;
289
+ state.user = msg.user != null ? msg.user : null;
290
+ if (msg.requestId) {
291
+ settlePending(msg.requestId, { ok: true, user: state.user });
292
+ }
213
293
  break;
214
- case BRIDGE.THEME:
294
+ case BRIDGE.THEME: {
295
+ const { type: _t, ...rest } = msg;
215
296
  state.theme = {
216
- mode: msg.mode || "light",
217
- primary_color: msg.primary_color || null
297
+ ...rest,
298
+ mode: rest.mode || "light",
299
+ primary_color: rest.primary_color != null ? rest.primary_color : null
218
300
  };
219
301
  applyTheme(state.theme);
220
302
  break;
221
- case BRIDGE.SAFE_AREA:
222
- state.safeArea = msg.insets || null;
303
+ }
304
+ case BRIDGE.SAFE_AREA: {
305
+ if (msg.insets != null) {
306
+ state.safeArea = msg.insets;
307
+ } else {
308
+ const { type: _t, ...rest } = msg;
309
+ state.safeArea = Object.keys(rest).length ? rest : null;
310
+ }
223
311
  break;
312
+ }
224
313
  case BRIDGE.HOST_INFO: {
225
- const { type: _t, ...rest } = msg;
314
+ const { type: _t, requestId, ...rest } = msg;
226
315
  state.hostInfo = rest;
316
+ if (requestId) {
317
+ settlePending(requestId, { ok: true, ...rest });
318
+ }
227
319
  break;
228
320
  }
229
- case BRIDGE.CAPABILITY_RESULT:
230
- state.lastCapability = {
231
- key: msg.key,
232
- granted: !!msg.granted,
233
- reason: msg.reason || null
321
+ case BRIDGE.PARAMS:
322
+ applyHostParams(msg);
323
+ break;
324
+ case BRIDGE.CAPABILITY_RESULT: {
325
+ const { type: _t, requestId, ...rest } = msg;
326
+ const result = {
327
+ ...rest,
328
+ key: rest.key,
329
+ granted: !!rest.granted,
330
+ reason: rest.reason != null ? rest.reason : null
234
331
  };
332
+ state.lastCapability = result;
333
+ if (requestId) {
334
+ settlePending(requestId, { ok: true, ...result });
335
+ }
235
336
  break;
337
+ }
236
338
  case BRIDGE.NAVIGATE_RESULT:
237
339
  settlePending(msg.requestId, {
238
340
  ok: !!msg.ok,
@@ -260,15 +362,36 @@ function handleHostMessage(raw) {
260
362
  emitLocalEvent(msg.name, msg.detail || {});
261
363
  break;
262
364
  default:
365
+ logWarn("unknown host message type", msg.type);
263
366
  break;
264
367
  }
265
368
  notify();
266
369
  }
267
370
  function onWindowMessage(event) {
371
+ if (allowedOrigins && allowedOrigins.length > 0) {
372
+ const origin = event && event.origin;
373
+ const allowAll = allowedOrigins.indexOf("*") !== -1;
374
+ const isOpaque = origin == null || origin === "" || origin === "null";
375
+ if (!allowAll && !isOpaque && allowedOrigins.indexOf(origin) === -1) {
376
+ logWarn("ignored message from disallowed origin", origin);
377
+ return;
378
+ }
379
+ }
268
380
  handleHostMessage(event && event.data);
269
381
  }
382
+ function setDebug(enabled) {
383
+ debugEnabled = !!enabled;
384
+ return ChoogaBridge;
385
+ }
270
386
  function init(options = {}) {
387
+ if (options.debug != null) {
388
+ debugEnabled = !!options.debug;
389
+ }
390
+ if (options.allowedOrigins != null) {
391
+ allowedOrigins = Array.isArray(options.allowedOrigins) ? options.allowedOrigins.slice() : null;
392
+ }
271
393
  if (initialized) {
394
+ logDebug("out", { note: "init() already called \u2014 updated debug/allowedOrigins only" });
272
395
  return ChoogaBridge;
273
396
  }
274
397
  initialized = true;
@@ -282,12 +405,164 @@ function init(options = {}) {
282
405
  document.addEventListener("message", onWindowMessage);
283
406
  }
284
407
  }
408
+ if (debugEnabled) {
409
+ logDebug("out", {
410
+ note: "bridge initialized",
411
+ version: BRIDGE_VERSION,
412
+ hostConnected: state.hostConnected,
413
+ allowedOrigins
414
+ });
415
+ }
285
416
  if (options.autoReady !== false) {
286
417
  ready();
287
418
  }
288
419
  notify();
289
420
  return ChoogaBridge;
290
421
  }
422
+ function mockHost(options = {}) {
423
+ if (typeof window === "undefined") {
424
+ return ChoogaBridge;
425
+ }
426
+ ensureChoogaHostShim();
427
+ mockHostActive = true;
428
+ state.hostConnected = true;
429
+ const delayMs = options.delayMs != null ? options.delayMs : 0;
430
+ const context = options.context || {};
431
+ function reply(msg) {
432
+ const run = () => handleHostMessage(msg);
433
+ if (delayMs > 0) {
434
+ setTimeout(run, delayMs);
435
+ } else {
436
+ run();
437
+ }
438
+ }
439
+ window.ReactNativeWebView = {
440
+ postMessage(payload) {
441
+ let msg = payload;
442
+ if (typeof payload === "string") {
443
+ try {
444
+ msg = JSON.parse(payload);
445
+ } catch (_) {
446
+ return;
447
+ }
448
+ }
449
+ if (!msg || typeof msg !== "object") {
450
+ return;
451
+ }
452
+ if (typeof options.onMessage === "function") {
453
+ options.onMessage(msg, reply);
454
+ return;
455
+ }
456
+ switch (msg.type) {
457
+ case BRIDGE.READY:
458
+ reply({
459
+ type: BRIDGE.SESSION,
460
+ token: "mock-token",
461
+ expires_at: null,
462
+ ...context.session || {}
463
+ });
464
+ reply({
465
+ type: BRIDGE.USER,
466
+ user: context.user || { id: "mock-user", name: "Demo User" }
467
+ });
468
+ reply({
469
+ type: BRIDGE.THEME,
470
+ mode: "light",
471
+ primary_color: null,
472
+ ...context.theme || {}
473
+ });
474
+ if (context.params) {
475
+ reply({
476
+ type: BRIDGE.PARAMS,
477
+ replace: true,
478
+ params: context.params
479
+ });
480
+ }
481
+ reply({
482
+ type: BRIDGE.HOST_INFO,
483
+ version: BRIDGE_VERSION,
484
+ host_version: BRIDGE_VERSION,
485
+ activities: context.activities || ["wallet.pick", "host.confirm"],
486
+ capabilities: context.capabilities || ["payments"],
487
+ granted: context.granted || ["payments"]
488
+ });
489
+ break;
490
+ case BRIDGE.ACTIVITY:
491
+ reply({
492
+ type: BRIDGE.ACTIVITY_RESULT,
493
+ requestId: msg.requestId,
494
+ ok: true,
495
+ name: msg.name,
496
+ mock: true,
497
+ ...msg.params || {}
498
+ });
499
+ break;
500
+ case BRIDGE.CALL:
501
+ reply({
502
+ type: BRIDGE.CALL_RESULT,
503
+ requestId: msg.requestId,
504
+ ok: true,
505
+ method: msg.method,
506
+ mock: true,
507
+ ...msg.params || {}
508
+ });
509
+ break;
510
+ case BRIDGE.NAVIGATE:
511
+ reply({
512
+ type: BRIDGE.NAVIGATE_RESULT,
513
+ requestId: msg.requestId,
514
+ ok: true,
515
+ reason: null
516
+ });
517
+ break;
518
+ case BRIDGE.USER_GET:
519
+ reply({
520
+ type: BRIDGE.USER,
521
+ requestId: msg.requestId,
522
+ user: context.user || { id: "mock-user", name: "Demo User" }
523
+ });
524
+ break;
525
+ case BRIDGE.HOST_INFO_GET:
526
+ reply({
527
+ type: BRIDGE.HOST_INFO,
528
+ requestId: msg.requestId,
529
+ version: BRIDGE_VERSION,
530
+ host_version: BRIDGE_VERSION,
531
+ activities: context.activities || ["wallet.pick", "host.confirm"],
532
+ capabilities: context.capabilities || ["payments"],
533
+ granted: context.granted || ["payments"]
534
+ });
535
+ break;
536
+ case BRIDGE.REQUEST_CAPABILITY:
537
+ reply({
538
+ type: BRIDGE.CAPABILITY_RESULT,
539
+ requestId: msg.requestId,
540
+ key: msg.key,
541
+ granted: true,
542
+ reason: null
543
+ });
544
+ break;
545
+ case BRIDGE.PAYMENTS_INITIATE:
546
+ if (msg.deliverResult === false) {
547
+ break;
548
+ }
549
+ reply({
550
+ type: BRIDGE.PAYMENTS_RESULT,
551
+ requestId: msg.requestId,
552
+ ok: true,
553
+ status: "succeeded",
554
+ reference: msg.reference || null,
555
+ mock: true
556
+ });
557
+ break;
558
+ default:
559
+ break;
560
+ }
561
+ }
562
+ };
563
+ notify();
564
+ return ChoogaBridge;
565
+ }
291
566
  function ready() {
292
567
  postToHost({ type: BRIDGE.READY });
293
568
  state.readySent = true;
@@ -299,8 +574,18 @@ function close() {
299
574
  function openExternal(url) {
300
575
  postToHost({ type: BRIDGE.OPEN_EXTERNAL, url });
301
576
  }
302
- function requestCapability(key) {
303
- postToHost({ type: BRIDGE.REQUEST_CAPABILITY, key });
577
+ function requestCapability(key, options = {}) {
578
+ const requestId = nextRequestId();
579
+ const promise = trackRequest({
580
+ requestId,
581
+ onResult: options.onResult,
582
+ timeoutMs: options.timeoutMs != null ? options.timeoutMs : 3e4
583
+ });
584
+ postToHost({ type: BRIDGE.REQUEST_CAPABILITY, requestId, key });
585
+ if (!detectHost()) {
586
+ settlePending(requestId, { ok: false, key, granted: false, reason: "no_host" });
587
+ }
588
+ return promise;
304
589
  }
305
590
  function showProgress(options = {}) {
306
591
  const opts = typeof options === "string" ? { message: options } : options || {};
@@ -418,7 +703,11 @@ function off(name, handler) {
418
703
  }
419
704
  function navigate(options = {}) {
420
705
  const requestId = nextRequestId();
421
- const promise = trackRequest({ requestId, timeoutMs: options.timeoutMs });
706
+ const promise = trackRequest({
707
+ requestId,
708
+ onResult: options.onResult,
709
+ timeoutMs: options.timeoutMs
710
+ });
422
711
  postToHost({
423
712
  type: BRIDGE.NAVIGATE,
424
713
  requestId,
@@ -501,6 +790,8 @@ var ChoogaBridge = {
501
790
  BRIDGE,
502
791
  BRIDGE_VERSION,
503
792
  init,
793
+ setDebug,
794
+ mockHost,
504
795
  ready,
505
796
  close,
506
797
  openExternal,