@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.
@@ -38,6 +38,8 @@ var BRIDGE = {
38
38
  CALL_RESULT: "chooga.callResult",
39
39
  EVENT: "chooga.event",
40
40
  HOST_INFO: "chooga.hostInfo",
41
+ /** Open bag of host-defined fields for getState() / subscribe. */
42
+ PARAMS: "chooga.params",
41
43
  // Mini-app → host
42
44
  READY: "chooga.ready",
43
45
  CLOSE: "chooga.close",
@@ -53,7 +55,22 @@ var BRIDGE = {
53
55
  HOST_INFO_GET: "chooga.hostInfo.get",
54
56
  CALL: "chooga.call"
55
57
  };
56
- var BRIDGE_VERSION = "1.2.0";
58
+ var BRIDGE_VERSION = "0.0.2";
59
+ var RESERVED_STATE_KEYS = /* @__PURE__ */ new Set([
60
+ "hostConnected",
61
+ "readySent",
62
+ "session",
63
+ "user",
64
+ "theme",
65
+ "safeArea",
66
+ "hostInfo",
67
+ "params",
68
+ "lastCapability",
69
+ "lastPayment",
70
+ "lastActivity",
71
+ "lastEvent",
72
+ "lastMessage"
73
+ ]);
57
74
  var state = {
58
75
  hostConnected: false,
59
76
  readySent: false,
@@ -62,6 +79,8 @@ var state = {
62
79
  theme: null,
63
80
  safeArea: null,
64
81
  hostInfo: null,
82
+ /** Host-defined open bag — any keys the host wants the mini-app to see. */
83
+ params: {},
65
84
  lastCapability: null,
66
85
  lastPayment: null,
67
86
  lastActivity: null,
@@ -73,12 +92,52 @@ var eventHandlers = /* @__PURE__ */ new Map();
73
92
  var pending = /* @__PURE__ */ new Map();
74
93
  var requestSeq = 0;
75
94
  var initialized = false;
95
+ var debugEnabled = false;
96
+ var allowedOrigins = null;
97
+ var mockHostActive = false;
76
98
  function nextRequestId() {
77
99
  requestSeq += 1;
78
100
  return `cb_${Date.now().toString(36)}_${requestSeq}`;
79
101
  }
102
+ function logDebug(direction, payload) {
103
+ if (!debugEnabled) {
104
+ return;
105
+ }
106
+ const label = direction === "out" ? "[ChoogaBridge \u2192 host]" : "[ChoogaBridge \u2190 host]";
107
+ try {
108
+ console.log(label, payload);
109
+ } catch (_) {
110
+ }
111
+ }
112
+ function logWarn(message, detail) {
113
+ if (!debugEnabled) {
114
+ return;
115
+ }
116
+ try {
117
+ console.warn("[ChoogaBridge]", message, detail);
118
+ } catch (_) {
119
+ }
120
+ }
121
+ function applyHostParams(msg) {
122
+ const { type: _t, replace, params: nested, ...rest } = msg || {};
123
+ const incoming = nested && typeof nested === "object" && !Array.isArray(nested) ? { ...nested } : { ...rest };
124
+ if (replace) {
125
+ state.params = incoming;
126
+ return;
127
+ }
128
+ const next = { ...state.params };
129
+ Object.keys(incoming).forEach((key) => {
130
+ if (incoming[key] === null) {
131
+ delete next[key];
132
+ } else {
133
+ next[key] = incoming[key];
134
+ }
135
+ });
136
+ state.params = next;
137
+ }
80
138
  function getState() {
81
- return {
139
+ const params = { ...state.params };
140
+ const known = {
82
141
  hostConnected: state.hostConnected,
83
142
  readySent: state.readySent,
84
143
  session: state.session,
@@ -86,12 +145,20 @@ function getState() {
86
145
  theme: state.theme,
87
146
  safeArea: state.safeArea,
88
147
  hostInfo: state.hostInfo,
148
+ params,
89
149
  lastCapability: state.lastCapability,
90
150
  lastPayment: state.lastPayment,
91
151
  lastActivity: state.lastActivity,
92
152
  lastEvent: state.lastEvent,
93
153
  lastMessage: state.lastMessage
94
154
  };
155
+ const extras = {};
156
+ Object.keys(params).forEach((key) => {
157
+ if (!RESERVED_STATE_KEYS.has(key)) {
158
+ extras[key] = params[key];
159
+ }
160
+ });
161
+ return { ...extras, ...known };
95
162
  }
96
163
  function notify() {
97
164
  const snapshot = getState();
@@ -124,6 +191,9 @@ function emitLocalEvent(name, detail) {
124
191
  }
125
192
  }
126
193
  function detectHost() {
194
+ if (mockHostActive) {
195
+ return true;
196
+ }
127
197
  return !!(typeof window !== "undefined" && window.ReactNativeWebView && typeof window.ReactNativeWebView.postMessage === "function");
128
198
  }
129
199
  function ensureChoogaHostShim() {
@@ -149,6 +219,7 @@ function ensureChoogaHostShim() {
149
219
  window.ChoogaTheme = window.ChoogaTheme || {};
150
220
  }
151
221
  function postToHost(msg) {
222
+ logDebug("out", msg);
152
223
  ensureChoogaHostShim();
153
224
  if (window.ChoogaHost && typeof window.ChoogaHost.postToHost === "function") {
154
225
  window.ChoogaHost.postToHost(msg);
@@ -202,6 +273,7 @@ function trackRequest(options = {}) {
202
273
  const timer = timeoutMs > 0 ? setTimeout(() => {
203
274
  if (pending.has(requestId)) {
204
275
  pending.delete(requestId);
276
+ logWarn("request timed out", { requestId, timeoutMs });
205
277
  reject(Object.assign(new Error("timeout"), { reason: "timeout" }));
206
278
  }
207
279
  }, timeoutMs) : null;
@@ -219,46 +291,76 @@ function handleHostMessage(raw) {
219
291
  try {
220
292
  msg = JSON.parse(raw);
221
293
  } catch (_) {
294
+ logWarn("ignored non-JSON host message", raw);
222
295
  return;
223
296
  }
224
297
  }
225
298
  if (!msg || typeof msg !== "object" || !msg.type) {
226
299
  return;
227
300
  }
301
+ logDebug("in", msg);
228
302
  state.hostConnected = true;
229
303
  state.lastMessage = msg;
230
304
  switch (msg.type) {
231
- case BRIDGE.SESSION:
305
+ case BRIDGE.SESSION: {
306
+ const { type: _t, ...rest } = msg;
232
307
  state.session = {
233
- token: msg.token || null,
234
- expires_at: msg.expires_at || null
308
+ ...rest,
309
+ token: rest.token != null ? rest.token : null,
310
+ expires_at: rest.expires_at != null ? rest.expires_at : null
235
311
  };
236
312
  break;
313
+ }
237
314
  case BRIDGE.USER:
238
- state.user = msg.user || null;
315
+ state.user = msg.user != null ? msg.user : null;
316
+ if (msg.requestId) {
317
+ settlePending(msg.requestId, { ok: true, user: state.user });
318
+ }
239
319
  break;
240
- case BRIDGE.THEME:
320
+ case BRIDGE.THEME: {
321
+ const { type: _t, ...rest } = msg;
241
322
  state.theme = {
242
- mode: msg.mode || "light",
243
- primary_color: msg.primary_color || null
323
+ ...rest,
324
+ mode: rest.mode || "light",
325
+ primary_color: rest.primary_color != null ? rest.primary_color : null
244
326
  };
245
327
  applyTheme(state.theme);
246
328
  break;
247
- case BRIDGE.SAFE_AREA:
248
- state.safeArea = msg.insets || null;
329
+ }
330
+ case BRIDGE.SAFE_AREA: {
331
+ if (msg.insets != null) {
332
+ state.safeArea = msg.insets;
333
+ } else {
334
+ const { type: _t, ...rest } = msg;
335
+ state.safeArea = Object.keys(rest).length ? rest : null;
336
+ }
249
337
  break;
338
+ }
250
339
  case BRIDGE.HOST_INFO: {
251
- const { type: _t, ...rest } = msg;
340
+ const { type: _t, requestId, ...rest } = msg;
252
341
  state.hostInfo = rest;
342
+ if (requestId) {
343
+ settlePending(requestId, { ok: true, ...rest });
344
+ }
253
345
  break;
254
346
  }
255
- case BRIDGE.CAPABILITY_RESULT:
256
- state.lastCapability = {
257
- key: msg.key,
258
- granted: !!msg.granted,
259
- reason: msg.reason || null
347
+ case BRIDGE.PARAMS:
348
+ applyHostParams(msg);
349
+ break;
350
+ case BRIDGE.CAPABILITY_RESULT: {
351
+ const { type: _t, requestId, ...rest } = msg;
352
+ const result = {
353
+ ...rest,
354
+ key: rest.key,
355
+ granted: !!rest.granted,
356
+ reason: rest.reason != null ? rest.reason : null
260
357
  };
358
+ state.lastCapability = result;
359
+ if (requestId) {
360
+ settlePending(requestId, { ok: true, ...result });
361
+ }
261
362
  break;
363
+ }
262
364
  case BRIDGE.NAVIGATE_RESULT:
263
365
  settlePending(msg.requestId, {
264
366
  ok: !!msg.ok,
@@ -286,15 +388,36 @@ function handleHostMessage(raw) {
286
388
  emitLocalEvent(msg.name, msg.detail || {});
287
389
  break;
288
390
  default:
391
+ logWarn("unknown host message type", msg.type);
289
392
  break;
290
393
  }
291
394
  notify();
292
395
  }
293
396
  function onWindowMessage(event) {
397
+ if (allowedOrigins && allowedOrigins.length > 0) {
398
+ const origin = event && event.origin;
399
+ const allowAll = allowedOrigins.indexOf("*") !== -1;
400
+ const isOpaque = origin == null || origin === "" || origin === "null";
401
+ if (!allowAll && !isOpaque && allowedOrigins.indexOf(origin) === -1) {
402
+ logWarn("ignored message from disallowed origin", origin);
403
+ return;
404
+ }
405
+ }
294
406
  handleHostMessage(event && event.data);
295
407
  }
408
+ function setDebug(enabled) {
409
+ debugEnabled = !!enabled;
410
+ return ChoogaBridge;
411
+ }
296
412
  function init(options = {}) {
413
+ if (options.debug != null) {
414
+ debugEnabled = !!options.debug;
415
+ }
416
+ if (options.allowedOrigins != null) {
417
+ allowedOrigins = Array.isArray(options.allowedOrigins) ? options.allowedOrigins.slice() : null;
418
+ }
297
419
  if (initialized) {
420
+ logDebug("out", { note: "init() already called \u2014 updated debug/allowedOrigins only" });
298
421
  return ChoogaBridge;
299
422
  }
300
423
  initialized = true;
@@ -308,12 +431,164 @@ function init(options = {}) {
308
431
  document.addEventListener("message", onWindowMessage);
309
432
  }
310
433
  }
434
+ if (debugEnabled) {
435
+ logDebug("out", {
436
+ note: "bridge initialized",
437
+ version: BRIDGE_VERSION,
438
+ hostConnected: state.hostConnected,
439
+ allowedOrigins
440
+ });
441
+ }
311
442
  if (options.autoReady !== false) {
312
443
  ready();
313
444
  }
314
445
  notify();
315
446
  return ChoogaBridge;
316
447
  }
448
+ function mockHost(options = {}) {
449
+ if (typeof window === "undefined") {
450
+ return ChoogaBridge;
451
+ }
452
+ ensureChoogaHostShim();
453
+ mockHostActive = true;
454
+ state.hostConnected = true;
455
+ const delayMs = options.delayMs != null ? options.delayMs : 0;
456
+ const context = options.context || {};
457
+ function reply(msg) {
458
+ const run = () => handleHostMessage(msg);
459
+ if (delayMs > 0) {
460
+ setTimeout(run, delayMs);
461
+ } else {
462
+ run();
463
+ }
464
+ }
465
+ window.ReactNativeWebView = {
466
+ postMessage(payload) {
467
+ let msg = payload;
468
+ if (typeof payload === "string") {
469
+ try {
470
+ msg = JSON.parse(payload);
471
+ } catch (_) {
472
+ return;
473
+ }
474
+ }
475
+ if (!msg || typeof msg !== "object") {
476
+ return;
477
+ }
478
+ if (typeof options.onMessage === "function") {
479
+ options.onMessage(msg, reply);
480
+ return;
481
+ }
482
+ switch (msg.type) {
483
+ case BRIDGE.READY:
484
+ reply({
485
+ type: BRIDGE.SESSION,
486
+ token: "mock-token",
487
+ expires_at: null,
488
+ ...context.session || {}
489
+ });
490
+ reply({
491
+ type: BRIDGE.USER,
492
+ user: context.user || { id: "mock-user", name: "Demo User" }
493
+ });
494
+ reply({
495
+ type: BRIDGE.THEME,
496
+ mode: "light",
497
+ primary_color: null,
498
+ ...context.theme || {}
499
+ });
500
+ if (context.params) {
501
+ reply({
502
+ type: BRIDGE.PARAMS,
503
+ replace: true,
504
+ params: context.params
505
+ });
506
+ }
507
+ reply({
508
+ type: BRIDGE.HOST_INFO,
509
+ version: BRIDGE_VERSION,
510
+ host_version: BRIDGE_VERSION,
511
+ activities: context.activities || ["wallet.pick", "host.confirm"],
512
+ capabilities: context.capabilities || ["payments"],
513
+ granted: context.granted || ["payments"]
514
+ });
515
+ break;
516
+ case BRIDGE.ACTIVITY:
517
+ reply({
518
+ type: BRIDGE.ACTIVITY_RESULT,
519
+ requestId: msg.requestId,
520
+ ok: true,
521
+ name: msg.name,
522
+ mock: true,
523
+ ...msg.params || {}
524
+ });
525
+ break;
526
+ case BRIDGE.CALL:
527
+ reply({
528
+ type: BRIDGE.CALL_RESULT,
529
+ requestId: msg.requestId,
530
+ ok: true,
531
+ method: msg.method,
532
+ mock: true,
533
+ ...msg.params || {}
534
+ });
535
+ break;
536
+ case BRIDGE.NAVIGATE:
537
+ reply({
538
+ type: BRIDGE.NAVIGATE_RESULT,
539
+ requestId: msg.requestId,
540
+ ok: true,
541
+ reason: null
542
+ });
543
+ break;
544
+ case BRIDGE.USER_GET:
545
+ reply({
546
+ type: BRIDGE.USER,
547
+ requestId: msg.requestId,
548
+ user: context.user || { id: "mock-user", name: "Demo User" }
549
+ });
550
+ break;
551
+ case BRIDGE.HOST_INFO_GET:
552
+ reply({
553
+ type: BRIDGE.HOST_INFO,
554
+ requestId: msg.requestId,
555
+ version: BRIDGE_VERSION,
556
+ host_version: BRIDGE_VERSION,
557
+ activities: context.activities || ["wallet.pick", "host.confirm"],
558
+ capabilities: context.capabilities || ["payments"],
559
+ granted: context.granted || ["payments"]
560
+ });
561
+ break;
562
+ case BRIDGE.REQUEST_CAPABILITY:
563
+ reply({
564
+ type: BRIDGE.CAPABILITY_RESULT,
565
+ requestId: msg.requestId,
566
+ key: msg.key,
567
+ granted: true,
568
+ reason: null
569
+ });
570
+ break;
571
+ case BRIDGE.PAYMENTS_INITIATE:
572
+ if (msg.deliverResult === false) {
573
+ break;
574
+ }
575
+ reply({
576
+ type: BRIDGE.PAYMENTS_RESULT,
577
+ requestId: msg.requestId,
578
+ ok: true,
579
+ status: "succeeded",
580
+ reference: msg.reference || null,
581
+ mock: true
582
+ });
583
+ break;
584
+ default:
585
+ break;
586
+ }
587
+ }
588
+ };
589
+ notify();
590
+ return ChoogaBridge;
591
+ }
317
592
  function ready() {
318
593
  postToHost({ type: BRIDGE.READY });
319
594
  state.readySent = true;
@@ -325,8 +600,18 @@ function close() {
325
600
  function openExternal(url) {
326
601
  postToHost({ type: BRIDGE.OPEN_EXTERNAL, url });
327
602
  }
328
- function requestCapability(key) {
329
- postToHost({ type: BRIDGE.REQUEST_CAPABILITY, key });
603
+ function requestCapability(key, options = {}) {
604
+ const requestId = nextRequestId();
605
+ const promise = trackRequest({
606
+ requestId,
607
+ onResult: options.onResult,
608
+ timeoutMs: options.timeoutMs != null ? options.timeoutMs : 3e4
609
+ });
610
+ postToHost({ type: BRIDGE.REQUEST_CAPABILITY, requestId, key });
611
+ if (!detectHost()) {
612
+ settlePending(requestId, { ok: false, key, granted: false, reason: "no_host" });
613
+ }
614
+ return promise;
330
615
  }
331
616
  function showProgress(options = {}) {
332
617
  const opts = typeof options === "string" ? { message: options } : options || {};
@@ -444,7 +729,11 @@ function off(name, handler) {
444
729
  }
445
730
  function navigate(options = {}) {
446
731
  const requestId = nextRequestId();
447
- const promise = trackRequest({ requestId, timeoutMs: options.timeoutMs });
732
+ const promise = trackRequest({
733
+ requestId,
734
+ onResult: options.onResult,
735
+ timeoutMs: options.timeoutMs
736
+ });
448
737
  postToHost({
449
738
  type: BRIDGE.NAVIGATE,
450
739
  requestId,
@@ -527,6 +816,8 @@ var ChoogaBridge = {
527
816
  BRIDGE,
528
817
  BRIDGE_VERSION,
529
818
  init,
819
+ setDebug,
820
+ mockHost,
530
821
  ready,
531
822
  close,
532
823
  openExternal,