@benji-money/connect-sdk 1.0.10 → 1.1.0-beta.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.
package/README.md CHANGED
@@ -90,6 +90,69 @@ The SDK accepts the following configuration options:
90
90
  - `onError` (optional): Callback function called when error occurs in the connect flow
91
91
  - `onExit` (optional): Callback function called when the user exits the connect flow
92
92
  - `onEvent` (optional): Callback function for handling various events
93
+ - `anchor` (optional): Live anchor binding — `HTMLElement`, CSS selector, getter `() => HTMLElement | null`, or ref `{ value: HTMLElement | null }`. Re-resolved on every layout pass. Falls back to centered when missing or invalid. See [Placement](#placement)
94
+ - `placement` (optional): Placement relative to `anchor` — see [Placement](#placement) below
95
+ - `offset` (optional): Pixel gap after placement — `{ top?: number; left?: number }` (default `{ top: 0, left: 0 }`)
96
+
97
+ ### Placement
98
+
99
+ Placement uses [Floating UI](https://floating-ui.com/docs/computePosition#placement) / CSS logical naming (`start` / `end`). In **LTR** layouts:
100
+
101
+ | Value | Meaning |
102
+ | ----- | ------- |
103
+ | `center` (default) | Full-viewport backdrop with iframe centered |
104
+ | `bottom-end` | Below the anchor, right edge aligned with anchor's right edge |
105
+ | `bottom-start` | Below the anchor, left edge aligned with anchor's left edge |
106
+
107
+ **Defaults:**
108
+
109
+ - No `anchor` → centered modal (unchanged behavior for existing integrations)
110
+ - `anchor` set, no `placement` → `bottom-end`
111
+ - If the anchor is missing or invalid on **initial open**, there is not enough viewport space below, or the viewport is narrower than the iframe (400px + padding), the SDK falls back to centered
112
+ - While the modal is **already open**, a temporarily hidden or detached anchor (e.g. crossing a responsive layout breakpoint) keeps the last anchored position instead of jumping to center; when the anchor becomes visible again, placement re-anchors automatically
113
+
114
+ **Live anchor bindings** (re-resolved on scroll, resize, and visual viewport changes):
115
+
116
+ | Form | Example | When to use |
117
+ | ---- | ------- | ----------- |
118
+ | CSS selector | `'#login-btn'` | Stable id on the anchor element |
119
+ | Ref object | `loginButtonRef` (Vue `{ value: el }`) | Anchor may re-render after async work |
120
+ | Getter | `() => document.querySelector('#login-btn')` | Custom lookup |
121
+ | `HTMLElement` | Snapshot at click time | Only when the node will not be replaced before `open()` |
122
+
123
+ Prefer selector, ref, or getter when the anchor can re-render (e.g. disabled/loading state) before the modal opens. A stale `HTMLElement` snapshot falls back to a centered modal on open instead of clipping to the top-left; while open, resize across breakpoints preserves the last anchored position until the anchor is visible again or the modal is closed.
124
+
125
+ **Viewport sizing:**
126
+
127
+ - Design size is **400×645** px (`IFRAME_WIDTH` / `IFRAME_HEIGHT`)
128
+ - **Centered** modals (default, explicit, or fallback) scale down proportionally when the viewport is smaller than the design size so the modal fits with 8px padding on each side
129
+ - **Anchored** popovers use the full design size when width and height checks pass; otherwise they fall back to a scaled centered modal
130
+ - Scroll, resize, and visual viewport changes while open recompute placement and size
131
+
132
+ **LOGIN popover example** (dropdown under a top-right button):
133
+
134
+ ```typescript
135
+ import {
136
+ ConnectSDK,
137
+ BenjiConnectMode,
138
+ BenjiConnectPlacement,
139
+ } from "@benji-money/connect-sdk";
140
+
141
+ // Prefer a live binding (selector or ref) when the button may re-render before open()
142
+ const sdk = new ConnectSDK({
143
+ environment: "sandbox",
144
+ token: connectToken,
145
+ mode: BenjiConnectMode.LOGIN,
146
+ anchor: "#login-btn",
147
+ placement: BenjiConnectPlacement.BOTTOM_END,
148
+ offset: { top: 8 },
149
+ onSuccess: (token) => { /* ... */ },
150
+ });
151
+
152
+ await sdk.open();
153
+ ```
154
+
155
+ > **`bottom-end`** opens below the anchor with the modal's right edge aligned to the anchor's right edge — matching a dropdown under a top-right LOGIN button.
93
156
 
94
157
  ### Environments
95
158
 
@@ -110,7 +110,7 @@ var ConnectSDK = (() => {
110
110
  // src/utils/config.ts
111
111
  var buildContext = () => ({
112
112
  namespace: "benji-connect-sdk" ? "benji-connect-sdk" : "benji-connect-sdk",
113
- version: "1.0.10" ? String("1.0.10") : "0.0.0"
113
+ version: "1.1.0-beta.2" ? String("1.1.0-beta.2") : "0.0.0"
114
114
  });
115
115
  function isBenjiConnectEnvironment(environment) {
116
116
  return environment === "local" /* LOCAL */ || environment === "development" /* DEVELOPMENT */ || environment === "sandbox" /* SANDBOX */ || environment === "production" /* PRODUCTION */;
@@ -130,11 +130,312 @@ var ConnectSDK = (() => {
130
130
  return defaultEnvironment;
131
131
  }
132
132
 
133
+ // src/utils/placement.ts
134
+ var IFRAME_WIDTH = 400;
135
+ var IFRAME_HEIGHT = 645;
136
+ var IFRAME_STATIC_STYLE = {
137
+ border: "none",
138
+ borderRadius: "20px",
139
+ overflow: "hidden"
140
+ };
141
+ var CONTAINER_BASE_STYLE = {
142
+ position: "fixed",
143
+ top: "0",
144
+ left: "0",
145
+ width: "100%",
146
+ height: "100%",
147
+ backgroundColor: "rgba(0,0,0,0.5)",
148
+ zIndex: "9999"
149
+ };
150
+ var VIEWPORT_PADDING = 8;
151
+ function isPreservableAnchoredLayout(layout) {
152
+ return layout != null && layout.placement !== "center" /* CENTER */;
153
+ }
154
+ function clampPreservedAnchoredLayout(layout, viewport) {
155
+ const maxLeft = viewport.width - layout.width - VIEWPORT_PADDING;
156
+ const maxTop = viewport.height - layout.height - VIEWPORT_PADDING;
157
+ const minLeft = VIEWPORT_PADDING;
158
+ const minTop = VIEWPORT_PADDING;
159
+ return {
160
+ ...layout,
161
+ left: maxLeft < minLeft ? minLeft : Math.min(Math.max(layout.left, minLeft), maxLeft),
162
+ top: maxTop < minTop ? minTop : Math.min(Math.max(layout.top, minTop), maxTop)
163
+ };
164
+ }
165
+ function preservedAnchoredLayoutOrCenter(viewport, options) {
166
+ const { preserveAnchoredLayout } = options;
167
+ if (isPreservableAnchoredLayout(preserveAnchoredLayout)) {
168
+ return clampPreservedAnchoredLayout(preserveAnchoredLayout, viewport);
169
+ }
170
+ return centerLayout(viewport);
171
+ }
172
+ function isRefLikeAnchor(anchor) {
173
+ return typeof anchor === "object" && anchor !== null && !(anchor instanceof HTMLElement) && "value" in anchor;
174
+ }
175
+ function resolveAnchor(anchor) {
176
+ if (!anchor) {
177
+ return null;
178
+ }
179
+ if (typeof anchor === "string") {
180
+ return document.querySelector(anchor);
181
+ }
182
+ if (typeof anchor === "function") {
183
+ const resolved = anchor();
184
+ return resolved instanceof HTMLElement ? resolved : null;
185
+ }
186
+ if (isRefLikeAnchor(anchor)) {
187
+ const el = anchor.value;
188
+ return el instanceof HTMLElement ? el : null;
189
+ }
190
+ return anchor instanceof HTMLElement ? anchor : null;
191
+ }
192
+ function isAnchorVisible(el) {
193
+ if (!el.isConnected) {
194
+ return false;
195
+ }
196
+ const style = window.getComputedStyle(el);
197
+ if (style.display === "none" || style.visibility === "hidden") {
198
+ return false;
199
+ }
200
+ const rect = el.getBoundingClientRect();
201
+ return rect.width > 0 || rect.height > 0;
202
+ }
203
+ function resolveEffectivePlacement(config) {
204
+ var _a;
205
+ if (!config.anchor) {
206
+ return "center" /* CENTER */;
207
+ }
208
+ return (_a = config.placement) != null ? _a : "bottom-end" /* BOTTOM_END */;
209
+ }
210
+ function defaultOffset(offset) {
211
+ var _a, _b;
212
+ return {
213
+ top: (_a = offset == null ? void 0 : offset.top) != null ? _a : 0,
214
+ left: (_b = offset == null ? void 0 : offset.left) != null ? _b : 0
215
+ };
216
+ }
217
+ function resolveIframeDimensions(viewport) {
218
+ const maxW = viewport.width - 2 * VIEWPORT_PADDING;
219
+ const maxH = viewport.height - 2 * VIEWPORT_PADDING;
220
+ const scale = Math.min(1, maxW / IFRAME_WIDTH, maxH / IFRAME_HEIGHT);
221
+ return {
222
+ width: Math.floor(IFRAME_WIDTH * scale),
223
+ height: Math.floor(IFRAME_HEIGHT * scale)
224
+ };
225
+ }
226
+ function iframeFitsHorizontally(viewportWidth, iframeWidth = IFRAME_WIDTH) {
227
+ return viewportWidth >= iframeWidth + 2 * VIEWPORT_PADDING;
228
+ }
229
+ function centerLayout(viewport) {
230
+ const { width, height } = resolveIframeDimensions(viewport);
231
+ return {
232
+ top: 0,
233
+ left: 0,
234
+ width,
235
+ height,
236
+ placement: "center" /* CENTER */
237
+ };
238
+ }
239
+ function fitsBelow(anchorBottom, offsetTop, viewportHeight, iframeHeight = IFRAME_HEIGHT) {
240
+ const top = anchorBottom + offsetTop;
241
+ const bottom = top + iframeHeight;
242
+ return top >= VIEWPORT_PADDING && bottom <= viewportHeight - VIEWPORT_PADDING;
243
+ }
244
+ function clampHorizontal(left, viewportWidth) {
245
+ const min = VIEWPORT_PADDING;
246
+ const max = viewportWidth - IFRAME_WIDTH - VIEWPORT_PADDING;
247
+ if (max < min) {
248
+ return min;
249
+ }
250
+ return Math.min(Math.max(left, min), max);
251
+ }
252
+ function warnInvalidAnchor(message) {
253
+ console.warn(`[Benji Connect SDK] ${message}`);
254
+ }
255
+ function computeIframeRect(anchorElement, placement, offset, viewport, options = {}) {
256
+ const { warnOnInvalidAnchor = false } = options;
257
+ const resolvedOffset = defaultOffset(offset);
258
+ if (placement === "center" /* CENTER */) {
259
+ return centerLayout(viewport);
260
+ }
261
+ if (!isAnchorVisible(anchorElement)) {
262
+ if (isPreservableAnchoredLayout(options.preserveAnchoredLayout)) {
263
+ return clampPreservedAnchoredLayout(
264
+ options.preserveAnchoredLayout,
265
+ viewport
266
+ );
267
+ }
268
+ if (warnOnInvalidAnchor) {
269
+ warnInvalidAnchor(
270
+ "anchor is hidden or detached; falling back to centered modal"
271
+ );
272
+ }
273
+ return centerLayout(viewport);
274
+ }
275
+ const rect = anchorElement.getBoundingClientRect();
276
+ if (!fitsBelow(rect.bottom, resolvedOffset.top, viewport.height)) {
277
+ return centerLayout(viewport);
278
+ }
279
+ if (!iframeFitsHorizontally(viewport.width)) {
280
+ return centerLayout(viewport);
281
+ }
282
+ const top = rect.bottom + resolvedOffset.top;
283
+ const idealLeft = placement === "bottom-end" /* BOTTOM_END */ ? rect.right - IFRAME_WIDTH + resolvedOffset.left : rect.left + resolvedOffset.left;
284
+ if (idealLeft < VIEWPORT_PADDING) {
285
+ if (warnOnInvalidAnchor) {
286
+ warnInvalidAnchor(
287
+ "anchor rect is unusable for placement; falling back to centered modal"
288
+ );
289
+ }
290
+ return centerLayout(viewport);
291
+ }
292
+ const left = clampHorizontal(idealLeft, viewport.width);
293
+ return {
294
+ top,
295
+ left,
296
+ width: IFRAME_WIDTH,
297
+ height: IFRAME_HEIGHT,
298
+ placement
299
+ };
300
+ }
301
+ function resolveIframeLayout(config, viewport = {
302
+ width: window.innerWidth,
303
+ height: window.innerHeight
304
+ }, options = {}) {
305
+ const { warnOnInvalidAnchor = true } = options;
306
+ const placement = resolveEffectivePlacement(config);
307
+ const anchorElement = resolveAnchor(config.anchor);
308
+ if (warnOnInvalidAnchor && placement !== "center" /* CENTER */ && !anchorElement) {
309
+ warnInvalidAnchor("anchor not found; falling back to centered modal");
310
+ return preservedAnchoredLayoutOrCenter(viewport, options);
311
+ }
312
+ if (placement === "center" /* CENTER */) {
313
+ return centerLayout(viewport);
314
+ }
315
+ if (!anchorElement) {
316
+ return preservedAnchoredLayoutOrCenter(viewport, options);
317
+ }
318
+ return computeIframeRect(
319
+ anchorElement,
320
+ placement,
321
+ config.offset,
322
+ viewport,
323
+ options
324
+ );
325
+ }
326
+ function applyOverlayBaseStyle(container) {
327
+ Object.assign(container.style, CONTAINER_BASE_STYLE);
328
+ }
329
+ function getVisualViewportOffset() {
330
+ const vv = window.visualViewport;
331
+ if (!vv) {
332
+ return { top: 0, left: 0 };
333
+ }
334
+ return { top: vv.offsetTop, left: vv.offsetLeft };
335
+ }
336
+ function getRepositionScrollTargets(anchor) {
337
+ const targets = /* @__PURE__ */ new Set([window]);
338
+ const anchorElement = resolveAnchor(anchor);
339
+ if (!anchorElement) {
340
+ return [...targets];
341
+ }
342
+ targets.add(anchorElement);
343
+ let parent = anchorElement.parentElement;
344
+ while (parent) {
345
+ const { overflow, overflowX, overflowY } = window.getComputedStyle(parent);
346
+ const overflowValue = `${overflow} ${overflowX} ${overflowY}`;
347
+ if (/(auto|scroll|overlay)/.test(overflowValue)) {
348
+ targets.add(parent);
349
+ }
350
+ parent = parent.parentElement;
351
+ }
352
+ return [...targets];
353
+ }
354
+ function applyIframeLayoutToElements(config, container, iframe, state, options = { warnOnInvalidAnchor: false }) {
355
+ state.layout = resolveIframeLayout(config, void 0, {
356
+ ...options,
357
+ preserveAnchoredLayout: state.lastValidAnchoredLayout
358
+ });
359
+ if (state.layout.placement !== "center" /* CENTER */) {
360
+ state.lastValidAnchoredLayout = state.layout;
361
+ }
362
+ const iframeStyle = {
363
+ ...IFRAME_STATIC_STYLE,
364
+ width: `${state.layout.width}px`,
365
+ height: `${state.layout.height}px`
366
+ };
367
+ if (state.layout.placement === "center" /* CENTER */) {
368
+ Object.assign(container.style, {
369
+ display: "flex",
370
+ alignItems: "center",
371
+ justifyContent: "center",
372
+ overflow: "auto"
373
+ });
374
+ Object.assign(iframe.style, iframeStyle, {
375
+ position: "relative",
376
+ top: "",
377
+ left: ""
378
+ });
379
+ return;
380
+ }
381
+ Object.assign(container.style, {
382
+ display: "block",
383
+ overflow: "auto"
384
+ });
385
+ const viewportOffset = getVisualViewportOffset();
386
+ Object.assign(iframe.style, iframeStyle, {
387
+ position: "fixed",
388
+ top: `${state.layout.top + viewportOffset.top}px`,
389
+ left: `${state.layout.left + viewportOffset.left}px`
390
+ });
391
+ }
392
+ function attachRepositionListeners(config, onReposition) {
393
+ const repositionHandler = onReposition;
394
+ const repositionScrollTargets = getRepositionScrollTargets(config.anchor);
395
+ repositionScrollTargets.forEach((target) => {
396
+ target.addEventListener("scroll", repositionHandler, { passive: true });
397
+ });
398
+ window.addEventListener("resize", repositionHandler);
399
+ const visualViewport = window.visualViewport;
400
+ visualViewport == null ? void 0 : visualViewport.addEventListener("resize", repositionHandler);
401
+ visualViewport == null ? void 0 : visualViewport.addEventListener("scroll", repositionHandler);
402
+ return {
403
+ remove() {
404
+ repositionScrollTargets.forEach((target) => {
405
+ target.removeEventListener("scroll", repositionHandler);
406
+ });
407
+ window.removeEventListener("resize", repositionHandler);
408
+ visualViewport == null ? void 0 : visualViewport.removeEventListener("resize", repositionHandler);
409
+ visualViewport == null ? void 0 : visualViewport.removeEventListener("scroll", repositionHandler);
410
+ }
411
+ };
412
+ }
413
+
414
+ // src/types/event.ts
415
+ var BenjiConnectEventType = /* @__PURE__ */ ((BenjiConnectEventType2) => {
416
+ BenjiConnectEventType2["AUTH_SUCCESS"] = "AUTH_SUCCESS";
417
+ BenjiConnectEventType2["FLOW_EXIT"] = "FLOW_EXIT";
418
+ BenjiConnectEventType2["FLOW_SUCCESS"] = "FLOW_SUCCESS";
419
+ BenjiConnectEventType2["EVENT"] = "EVENT";
420
+ BenjiConnectEventType2["ERROR"] = "ERROR";
421
+ return BenjiConnectEventType2;
422
+ })(BenjiConnectEventType || {});
423
+
133
424
  // src/utils/auth.ts
425
+ var CONNECT_MESSAGE_TYPES = new Set(Object.values(BenjiConnectEventType));
426
+ function isConnectMessageType(data2) {
427
+ if (!data2 || typeof data2 !== "object") return false;
428
+ const type = data2.type;
429
+ return typeof type === "string" && CONNECT_MESSAGE_TYPES.has(type);
430
+ }
134
431
  var extractAccessToken = (token) => {
135
432
  var _a;
136
433
  return typeof token === "string" ? token : (_a = token == null ? void 0 : token.access_token) != null ? _a : "";
137
434
  };
435
+ var extractExchangeToken = (token) => {
436
+ var _a;
437
+ return typeof token === "string" ? token : (_a = token == null ? void 0 : token.exchange_token) != null ? _a : "";
438
+ };
138
439
  var mapEventToConnectToken = (data2) => {
139
440
  var _a, _b, _c;
140
441
  return {
@@ -202,7 +503,7 @@ var ConnectSDK = (() => {
202
503
  };
203
504
  };
204
505
  var mapToOnSuccessData = (data2) => {
205
- const token = extractAccessToken(data2.token);
506
+ const token = data2.action === "login" /* Login */ ? extractExchangeToken(data2.token) : extractAccessToken(data2.token);
206
507
  let metadata = {
207
508
  context: buildContext(),
208
509
  action: data2.action,
@@ -249,6 +550,9 @@ var ConnectSDK = (() => {
249
550
  this.onEvent = config.onEvent;
250
551
  this.close = config.close;
251
552
  }
553
+ static setMode(mode) {
554
+ this.mode = mode;
555
+ }
252
556
  static addEventListeners() {
253
557
  if (this.configuredListeners) return;
254
558
  window.addEventListener("error", this.errorEventHandler);
@@ -261,6 +565,7 @@ var ConnectSDK = (() => {
261
565
  window.removeEventListener("unhandledrejection", this.errorEventHandler);
262
566
  window.removeEventListener("message", this.messageEventHandler);
263
567
  this.configuredListeners = false;
568
+ this.mode = 1 /* CONNECT */;
264
569
  }
265
570
  static handleErrorMessage(event) {
266
571
  switch (event.type) {
@@ -281,8 +586,8 @@ var ConnectSDK = (() => {
281
586
  }
282
587
  }
283
588
  static handleMessage(event) {
284
- var _a, _b, _c, _d, _e, _f;
285
- console.debug("[Benji Connect SDK] Received message", event);
589
+ var _a, _b, _c, _d, _e, _f, _g;
590
+ if (!isConnectMessageType(event.data)) return;
286
591
  const expectedOrigin = new URL(Endpoints.benji_connect_auth_url).origin;
287
592
  if (!event || event.origin !== expectedOrigin) {
288
593
  console.warn("[Benji Connect SDK] Skipped message, received event from unexpected origin", event.origin, expectedOrigin);
@@ -312,18 +617,21 @@ var ConnectSDK = (() => {
312
617
  const connectMessage = message;
313
618
  const callbackData = BenjiConnectCallbackMapperMap.FLOW_SUCCESS(connectMessage.data);
314
619
  (_d = this.onSuccess) == null ? void 0 : _d.call(this, callbackData.token, callbackData.metadata);
620
+ if (this.mode === 5 /* LOGIN */) {
621
+ (_e = this.close) == null ? void 0 : _e.call(this);
622
+ }
315
623
  break;
316
624
  }
317
625
  case "ERROR" /* ERROR */: {
318
626
  const connectMessage = message;
319
627
  const callbackData = BenjiConnectCallbackMapperMap.ERROR(connectMessage.data);
320
- (_e = this.onError) == null ? void 0 : _e.call(this, callbackData.error, callbackData.error_id, callbackData.metadata);
628
+ (_f = this.onError) == null ? void 0 : _f.call(this, callbackData.error, callbackData.error_id, callbackData.metadata);
321
629
  break;
322
630
  }
323
- default: {
324
- const m = message;
325
- const callbackData = mapToOnEventData(m, m.data);
326
- (_f = this.onEvent) == null ? void 0 : _f.call(this, callbackData.type, callbackData.metadata);
631
+ case "EVENT" /* EVENT */: {
632
+ const connectMessage = message;
633
+ const callbackData = mapToOnEventData(connectMessage, connectMessage.data);
634
+ (_g = this.onEvent) == null ? void 0 : _g.call(this, callbackData.type, callbackData.metadata);
327
635
  break;
328
636
  }
329
637
  }
@@ -332,6 +640,7 @@ var ConnectSDK = (() => {
332
640
  }
333
641
  };
334
642
  _MessageRouter.configuredListeners = false;
643
+ _MessageRouter.mode = 1 /* CONNECT */;
335
644
  _MessageRouter.errorEventHandler = (event) => {
336
645
  return _MessageRouter.handleErrorMessage(event);
337
646
  };
@@ -345,6 +654,17 @@ var ConnectSDK = (() => {
345
654
  constructor(config) {
346
655
  this.iframe = null;
347
656
  this.container = null;
657
+ this.layoutState = {
658
+ layout: {
659
+ top: 0,
660
+ left: 0,
661
+ width: IFRAME_WIDTH,
662
+ height: IFRAME_HEIGHT,
663
+ placement: "center" /* CENTER */
664
+ },
665
+ lastValidAnchoredLayout: null
666
+ };
667
+ this.repositionListeners = null;
348
668
  this.sdkConfig = config;
349
669
  if (!this.sdkConfig.environment) throw new Error("Environment is required");
350
670
  if (!this.sdkConfig.token) throw new Error("Connect token is required");
@@ -362,44 +682,48 @@ var ConnectSDK = (() => {
362
682
  });
363
683
  }
364
684
  async open() {
685
+ var _a;
365
686
  await this.initialize();
366
687
  this.openUI();
688
+ MessageRouter.setMode((_a = this.sdkConfig.mode) != null ? _a : 1 /* CONNECT */);
367
689
  MessageRouter.addEventListeners();
368
690
  }
369
691
  openUI() {
370
692
  if (this.iframe) return;
371
693
  this.container = document.createElement("div");
372
- Object.assign(this.container.style, {
373
- position: "fixed",
374
- top: "0",
375
- left: "0",
376
- width: "100%",
377
- height: "100%",
378
- backgroundColor: "rgba(0,0,0,0.5)",
379
- zIndex: "9999",
380
- display: "flex",
381
- alignItems: "center",
382
- justifyContent: "center",
383
- overflow: "auto"
384
- });
694
+ applyOverlayBaseStyle(this.container);
385
695
  console.debug("[Connect SDK] endpoints", Endpoints);
386
696
  console.log("[Connect SDK] opeining URL", Endpoints.benji_connect_auth_url);
387
697
  const url = new URL(Endpoints.benji_connect_auth_url);
388
698
  const connectToken = this.sdkConfig.token;
389
699
  url.searchParams.set("connect_token", connectToken);
390
700
  url.searchParams.set("t", String(Date.now()));
701
+ if (this.sdkConfig.mode != null) {
702
+ url.searchParams.set("mode", String(this.sdkConfig.mode));
703
+ }
391
704
  this.iframe = document.createElement("iframe");
392
- Object.assign(this.iframe.style, {
393
- position: "relative",
394
- width: "400px",
395
- height: "645px",
396
- border: "none",
397
- borderRadius: "20px",
398
- overflow: "hidden"
399
- });
400
705
  this.iframe.src = url.toString();
401
706
  this.container.appendChild(this.iframe);
402
707
  document.body.appendChild(this.container);
708
+ applyIframeLayoutToElements(
709
+ this.sdkConfig,
710
+ this.container,
711
+ this.iframe,
712
+ this.layoutState,
713
+ { warnOnInvalidAnchor: true }
714
+ );
715
+ this.repositionListeners = attachRepositionListeners(
716
+ this.sdkConfig,
717
+ () => {
718
+ if (!this.iframe || !this.container) return;
719
+ applyIframeLayoutToElements(
720
+ this.sdkConfig,
721
+ this.container,
722
+ this.iframe,
723
+ this.layoutState
724
+ );
725
+ }
726
+ );
403
727
  this.container.addEventListener("click", (e) => {
404
728
  var _a, _b;
405
729
  if (e.target === this.container) {
@@ -412,8 +736,14 @@ var ConnectSDK = (() => {
412
736
  });
413
737
  }
414
738
  close() {
739
+ var _a, _b;
415
740
  if (!this.iframe) return;
416
- document.body.removeChild(this.container);
741
+ (_a = this.repositionListeners) == null ? void 0 : _a.remove();
742
+ this.repositionListeners = null;
743
+ this.layoutState.lastValidAnchoredLayout = null;
744
+ if ((_b = this.container) == null ? void 0 : _b.parentNode) {
745
+ this.container.parentNode.removeChild(this.container);
746
+ }
417
747
  MessageRouter.removeEventListeners();
418
748
  this.iframe = null;
419
749
  this.container = null;