@superleapai/flow-ui 2.5.13 → 2.5.14

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/core/bridge.js CHANGED
@@ -27,7 +27,7 @@
27
27
  // Closure state (reset on each connect / disconnect cycle)
28
28
  // ---------------------------------------------------------------------------
29
29
 
30
- var _instanceId = null;
30
+ var _bridgeId = null;
31
31
  var _nonce = null;
32
32
  var _port = null; // MessagePort (iframe transport, post-handshake)
33
33
  var _connected = false;
@@ -44,47 +44,6 @@
44
44
  // Helpers – crypto
45
45
  // ---------------------------------------------------------------------------
46
46
 
47
- function generateUUID() {
48
- // crypto.randomUUID where available (modern browsers)
49
- if (
50
- typeof crypto !== "undefined" &&
51
- typeof crypto.randomUUID === "function"
52
- ) {
53
- return crypto.randomUUID();
54
- }
55
- // Fallback: manual v4 UUID via getRandomValues or Math.random
56
- var bytes;
57
- if (
58
- typeof crypto !== "undefined" &&
59
- typeof crypto.getRandomValues === "function"
60
- ) {
61
- bytes = new Uint8Array(16);
62
- crypto.getRandomValues(bytes);
63
- } else {
64
- bytes = new Uint8Array(16);
65
- for (var i = 0; i < 16; i++) {
66
- bytes[i] = (Math.random() * 256) | 0;
67
- }
68
- }
69
- bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4
70
- bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant 1
71
- var hex = [];
72
- for (var j = 0; j < 16; j++) {
73
- hex.push(("0" + bytes[j].toString(16)).slice(-2));
74
- }
75
- return (
76
- hex.slice(0, 4).join("") +
77
- "-" +
78
- hex.slice(4, 6).join("") +
79
- "-" +
80
- hex.slice(6, 8).join("") +
81
- "-" +
82
- hex.slice(8, 10).join("") +
83
- "-" +
84
- hex.slice(10).join("")
85
- );
86
- }
87
-
88
47
  function generateNonce() {
89
48
  var bytes;
90
49
  if (
@@ -135,7 +94,7 @@
135
94
  function createEnvelope(action, payload) {
136
95
  return {
137
96
  type: MESSAGE_TYPE,
138
- instanceId: _instanceId,
97
+ bridgeId: _bridgeId,
139
98
  action: action,
140
99
  payload: payload || {},
141
100
  };
@@ -185,8 +144,8 @@
185
144
  function handleIncomingMessage(data, origin) {
186
145
  // Ignore messages that aren't ours
187
146
  if (!data || data.type !== MESSAGE_TYPE) return;
188
- // Ignore messages for a different instance
189
- if (data.instanceId && data.instanceId !== _instanceId) return;
147
+ // Ignore messages for a different bridge
148
+ if (data.bridgeId && data.bridgeId !== _bridgeId) return;
190
149
 
191
150
  var action = data.action;
192
151
 
@@ -319,7 +278,7 @@
319
278
  if (_transport === "react-native" && global.__superleapBridge) {
320
279
  delete global.__superleapBridge;
321
280
  }
322
- _instanceId = null;
281
+ _bridgeId = null;
323
282
  _nonce = null;
324
283
  _connected = false;
325
284
  _transport = null;
@@ -337,10 +296,10 @@
337
296
  * Initiate a handshake with the host CRM.
338
297
  *
339
298
  * @param {Object} options
340
- * @param {string} options.flowId identifies this flow to the CRM
341
- * @param {string} [options.version] – library version (informational)
299
+ * @param {string} [options.bridgeId] explicit bridgeId (auto-read from URL if omitted)
300
+ * @param {string} [options.version] – library version (informational)
342
301
  * @param {string} [options.crmOrigin] – expected CRM origin for validation
343
- * @param {number} [options.timeout] – handshake timeout in ms (default 5000)
302
+ * @param {number} [options.timeout] – handshake timeout in ms (default 5000)
344
303
  * @returns {Promise<Object>} resolves with the CRM's handshake-ack payload
345
304
  */
346
305
  function connect(options) {
@@ -362,7 +321,18 @@
362
321
  return;
363
322
  }
364
323
 
365
- _instanceId = generateUUID();
324
+ // Read bridgeId from URL query param (set by CRM) or explicit option
325
+ var urlParams = new URLSearchParams(global.location.search);
326
+ _bridgeId = opts.bridgeId || urlParams.get("_bridgeId");
327
+ if (!_bridgeId) {
328
+ reject(
329
+ new Error(
330
+ "SuperleapBridge: No _bridgeId found in URL. Is this page embedded in the SuperLeap CRM?",
331
+ ),
332
+ );
333
+ return;
334
+ }
335
+
366
336
  _nonce = generateNonce();
367
337
  _crmOrigin = opts.crmOrigin || null;
368
338
  _pendingResolve = resolve;
@@ -409,9 +379,8 @@
409
379
 
410
380
  // --- Send handshake-request ---
411
381
  var envelope = createEnvelope("handshake-request", {
412
- flowId: opts.flowId || "",
413
- version: opts.version || "",
414
382
  nonce: _nonce,
383
+ version: opts.version || "",
415
384
  });
416
385
  sendRaw(envelope);
417
386
 
@@ -500,8 +469,8 @@
500
469
  /**
501
470
  * @returns {string|null}
502
471
  */
503
- function getInstanceId() {
504
- return _instanceId;
472
+ function getBridgeId() {
473
+ return _bridgeId;
505
474
  }
506
475
 
507
476
  // ---------------------------------------------------------------------------
@@ -515,7 +484,7 @@
515
484
  onMessage: onMessage,
516
485
  offMessage: offMessage,
517
486
  isConnected: isConnected,
518
- getInstanceId: getInstanceId,
487
+ getBridgeId: getBridgeId,
519
488
  };
520
489
 
521
490
  if (global) {
package/core/crm.js CHANGED
@@ -10,18 +10,9 @@
10
10
  * - Context management (orgId, userId, etc.)
11
11
  * - Custom event send / listen with namespacing
12
12
  *
13
- * Usage (inside an iframe that imports superleap-flow):
14
- *
15
- * const { context, config } = await SuperLeap.connect({
16
- * flowId: 'my-onboarding-form',
17
- * });
18
- * // SDK is now initialized, context has orgId/userId/etc.
19
- *
20
- * SuperLeap.toast('Record saved!', 'success');
21
- * SuperLeap.closeForm();
22
- *
23
- * SuperLeap.on('prefill', (data) => { ... });
24
- * SuperLeap.send('form-submitted', { values });
13
+ * The library auto-connects when loaded inside a CRM iframe (bridgeId is
14
+ * read from the URL). User code should listen for the 'superleap-flow:ready'
15
+ * event — by the time it fires, the SDK is initialized and context is available.
25
16
  *
26
17
  * This extends the existing SuperLeap API from superleapClient.js which
27
18
  * already has: init(), getSdk(), isAvailable(), getDefaultConfig().
@@ -38,6 +29,7 @@
38
29
  var _config = null; // full config from CRM
39
30
  var _bridge = null; // resolved reference to SuperleapBridge
40
31
  var _connected = false;
32
+ var _connectPromise = null; // in-flight connect promise (deduplication)
41
33
 
42
34
  // Library version — sent during handshake so the CRM knows what it's talking to
43
35
  var LIB_VERSION = "2.1.0";
@@ -100,14 +92,27 @@
100
92
  * credentials and context, and (by default) auto-initializes the
101
93
  * SuperLeap SDK.
102
94
  *
103
- * @param {Object} options
104
- * @param {string} options.flowId – identifies this flow to the CRM
105
- * @param {string} [options.crmOrigin] – expected CRM origin for validation
106
- * @param {boolean} [options.autoInit] – auto-call SuperLeap.init() (default true)
107
- * @param {number} [options.timeout] – handshake timeout in ms (default 5000)
95
+ * If already connected, silently resolves with the existing context/config.
96
+ * If a connection is in flight (e.g. auto-connect), returns the same promise.
97
+ *
98
+ * @param {Object} [options]
99
+ * @param {string} [options.bridgeId] – explicit bridgeId override (auto-read from URL)
100
+ * @param {string} [options.crmOrigin] – expected CRM origin for validation
101
+ * @param {boolean} [options.autoInit] – auto-call SuperLeap.init() (default true)
102
+ * @param {number} [options.timeout] – handshake timeout in ms (default 5000)
108
103
  * @returns {Promise<{ context: Object, config: Object }>}
109
104
  */
110
105
  function connect(options) {
106
+ // Already connected — silently resolve
107
+ if (_connected) {
108
+ return Promise.resolve({ context: _context, config: _config });
109
+ }
110
+
111
+ // Connection in flight (e.g. auto-connect started) — return same promise
112
+ if (_connectPromise) {
113
+ return _connectPromise;
114
+ }
115
+
111
116
  var opts = options || {};
112
117
  var bridge = getBridge();
113
118
 
@@ -117,17 +122,9 @@
117
122
  );
118
123
  }
119
124
 
120
- if (_connected) {
121
- return Promise.reject(
122
- new Error(
123
- "SuperleapCRM: Already connected. Call disconnect() first.",
124
- ),
125
- );
126
- }
127
-
128
- return bridge
125
+ _connectPromise = bridge
129
126
  .connect({
130
- flowId: opts.flowId || "",
127
+ bridgeId: opts.bridgeId,
131
128
  version: LIB_VERSION,
132
129
  crmOrigin: opts.crmOrigin,
133
130
  timeout: opts.timeout,
@@ -146,6 +143,7 @@
146
143
  }
147
144
 
148
145
  _connected = true;
146
+ _connectPromise = null;
149
147
 
150
148
  // Listen for context pushes from the CRM
151
149
  bridge.onMessage("data:context", function (newCtx) {
@@ -169,7 +167,13 @@
169
167
  }
170
168
 
171
169
  return { context: _context, config: _config };
170
+ })
171
+ .catch(function (err) {
172
+ _connectPromise = null;
173
+ throw err;
172
174
  });
175
+
176
+ return _connectPromise;
173
177
  }
174
178
 
175
179
  /**
@@ -190,6 +194,7 @@
190
194
  _context = null;
191
195
  _config = null;
192
196
  _connected = false;
197
+ _connectPromise = null;
193
198
  _bridge = null;
194
199
  }
195
200