@benji-money/connect-sdk 1.1.4-beta.4 → 1.1.4-beta.6

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.
@@ -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.1.4-beta.4" ? String("1.1.4-beta.4") : "0.0.0"
113
+ version: "1.1.4-beta.6" ? String("1.1.4-beta.6") : "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 */;
@@ -247,6 +247,150 @@ var ConnectSDK = (() => {
247
247
  return ensureAuthLinkStateNonce(url, nonce);
248
248
  }
249
249
 
250
+ // src/utils/verifyAppUrl.ts
251
+ var DEFAULT_LOCAL_VERIFY_ORIGIN = "http://localhost:3000";
252
+ var REMOTE_VERIFY_ORIGINS = [
253
+ "https://verifyapp-staging.withbenji.com",
254
+ "https://verifyapp-dev.withbenji.com",
255
+ "https://verifyapp.withbenji.com"
256
+ ];
257
+ function rewriteVerifyAppUrlForLocalConnect(partnerLoginLink, localOrigin = DEFAULT_LOCAL_VERIFY_ORIGIN) {
258
+ if (!partnerLoginLink) {
259
+ return partnerLoginLink;
260
+ }
261
+ let url = partnerLoginLink;
262
+ for (const remote of REMOTE_VERIFY_ORIGINS) {
263
+ url = url.replaceAll(remote, localOrigin);
264
+ url = url.replaceAll(
265
+ remote.replace(/^https:\/\//, "https%3A//"),
266
+ localOrigin.replace(/^http:\/\//, "http%3A//")
267
+ );
268
+ url = url.replaceAll(encodeURIComponent(remote), encodeURIComponent(localOrigin));
269
+ }
270
+ return url;
271
+ }
272
+
273
+ // src/utils/loginTransport.ts
274
+ var LOGIN_PENDING_STORAGE_KEY = "benji_login_pending";
275
+ var LOGIN_FORCE_REDIRECT_STORAGE_KEY = "benji_force_login_redirect";
276
+ var LOGIN_RESUME_QUERY_FLAG = "login_resume";
277
+ var LOGIN_RETURN_STATE_SEPARATOR = ".benji_return.";
278
+ function isIosSafari(userAgent, maxTouchPoints) {
279
+ const isIos = /iPhone|iPad|iPod/.test(userAgent) || userAgent.includes("Macintosh") && maxTouchPoints > 1;
280
+ const isSafari = /Safari/.test(userAgent) && !/CriOS|FxiOS|EdgiOS|OPiOS|Chrome/.test(userAgent);
281
+ return isIos && isSafari;
282
+ }
283
+ function shouldUseSameTabLogin(args) {
284
+ if (!args.isNonDummyLogin) return false;
285
+ if (args.forceRedirect) return true;
286
+ return isIosSafari(args.userAgent, args.maxTouchPoints);
287
+ }
288
+ function isNonDummyLogin(mode, authType) {
289
+ return mode === 5 /* LOGIN */ && authType != null && authType !== 0 /* DUMMY */;
290
+ }
291
+ function isLocalForceRedirectEnabled(environment, storage) {
292
+ if (environment !== "local" /* LOCAL */) return false;
293
+ if (!storage) return false;
294
+ return storage.getItem(LOGIN_FORCE_REDIRECT_STORAGE_KEY) === "1";
295
+ }
296
+ function canStampLoginReturnState(partnerAuthLink, configToken) {
297
+ try {
298
+ const state = new URL(partnerAuthLink).searchParams.get("state");
299
+ return !state || state === configToken;
300
+ } catch {
301
+ return false;
302
+ }
303
+ }
304
+ function writeLoginPending(storage, payload) {
305
+ storage.setItem(LOGIN_PENDING_STORAGE_KEY, JSON.stringify(payload));
306
+ }
307
+ function buildRequestCodeExchangeMessage(args) {
308
+ const { configToken } = decodeLoginReturnState(args.token);
309
+ return {
310
+ type: "request_code_exchange",
311
+ data: {
312
+ partnerId: args.partnerId,
313
+ code: args.code,
314
+ token: configToken,
315
+ redirect_uri: args.redirectUri
316
+ }
317
+ };
318
+ }
319
+ function toBase64Url(value) {
320
+ const bytes = new TextEncoder().encode(value);
321
+ let binary = "";
322
+ for (const byte of bytes) {
323
+ binary += String.fromCharCode(byte);
324
+ }
325
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
326
+ }
327
+ function fromBase64Url(value) {
328
+ try {
329
+ const padded = value.replace(/-/g, "+").replace(/_/g, "/");
330
+ const pad = padded.length % 4 === 0 ? "" : "=".repeat(4 - padded.length % 4);
331
+ const binary = atob(padded + pad);
332
+ const bytes = Uint8Array.from(binary, (ch) => ch.charCodeAt(0));
333
+ return new TextDecoder().decode(bytes);
334
+ } catch {
335
+ return null;
336
+ }
337
+ }
338
+ function encodeLoginReturnState(configToken, returnOrigin) {
339
+ return `${configToken}${LOGIN_RETURN_STATE_SEPARATOR}${toBase64Url(returnOrigin)}`;
340
+ }
341
+ function decodeLoginReturnState(state) {
342
+ const separatorIndex = state.indexOf(LOGIN_RETURN_STATE_SEPARATOR);
343
+ if (separatorIndex <= 0) {
344
+ return { configToken: state };
345
+ }
346
+ const configToken = state.slice(0, separatorIndex);
347
+ const encodedOrigin = state.slice(
348
+ separatorIndex + LOGIN_RETURN_STATE_SEPARATOR.length
349
+ );
350
+ if (!configToken || !encodedOrigin) {
351
+ return { configToken: state };
352
+ }
353
+ const returnOrigin = fromBase64Url(encodedOrigin);
354
+ if (!returnOrigin) {
355
+ return { configToken: state };
356
+ }
357
+ return { configToken, returnOrigin };
358
+ }
359
+ function stampLoginReturnState(url, configToken, returnOrigin) {
360
+ if (!canStampLoginReturnState(url, configToken)) {
361
+ throw new Error("Partner sign-in URL contains incompatible OAuth state.");
362
+ }
363
+ const state = encodeLoginReturnState(configToken, returnOrigin);
364
+ try {
365
+ const parsed = new URL(url);
366
+ parsed.searchParams.set("state", state);
367
+ return parsed.toString();
368
+ } catch {
369
+ if (url.includes("state=")) {
370
+ return url.replace(
371
+ /([?&]state=)[^&]*/,
372
+ `$1${encodeURIComponent(state)}`
373
+ );
374
+ }
375
+ const separator = url.includes("?") ? "&" : "?";
376
+ return `${url}${separator}state=${encodeURIComponent(state)}`;
377
+ }
378
+ }
379
+ function buildSameTabPartnerAuthUrl(args) {
380
+ let prepared = args.configToken ? preparePartnerAuthUrl(args.partnerAuthLink, args.configToken) : args.partnerAuthLink;
381
+ if (args.configToken && args.returnOrigin) {
382
+ prepared = stampLoginReturnState(
383
+ prepared,
384
+ args.configToken,
385
+ args.returnOrigin
386
+ );
387
+ }
388
+ if (!args.rewriteForLocalConnect) {
389
+ return prepared;
390
+ }
391
+ return rewriteVerifyAppUrlForLocalConnect(prepared);
392
+ }
393
+
250
394
  // src/utils/placement.ts
251
395
  var IFRAME_WIDTH = 400;
252
396
  var IFRAME_HEIGHT = 645;
@@ -711,10 +855,11 @@ var ConnectSDK = (() => {
711
855
  "request_code_exchange",
712
856
  "request_code_with_login",
713
857
  "request_code_with_phone",
714
- "request_loyalty_verification",
715
- "request_phone_verification"
858
+ "request_phone_verification",
859
+ "request_loyalty_verification"
716
860
  ]);
717
861
  var CONNECT_MESSAGE_TYPES = new Set(Object.values(BenjiConnectEventType));
862
+ var LOGIN_PARTNER_POPUP_NAME = "benji_partner_auth";
718
863
  function isAuthMessageType(data2) {
719
864
  if (!data2 || typeof data2 !== "object") return false;
720
865
  const type = data2.type;
@@ -723,9 +868,18 @@ var ConnectSDK = (() => {
723
868
  function shouldRelayMessage(mode, data2) {
724
869
  return mode === 5 /* LOGIN */ && isAuthMessageType(data2);
725
870
  }
871
+ function isMessageWindow(source) {
872
+ if (!source || typeof source !== "object") return false;
873
+ try {
874
+ return typeof source.closed === "boolean";
875
+ } catch {
876
+ return false;
877
+ }
878
+ }
726
879
  function sourceOpenerIsHost(source) {
727
880
  try {
728
- return source instanceof Window && source.opener === window;
881
+ if (!isMessageWindow(source)) return false;
882
+ return source.opener === window;
729
883
  } catch {
730
884
  return false;
731
885
  }
@@ -740,7 +894,53 @@ var ConnectSDK = (() => {
740
894
  if (sourceOpenerIsHost(event.source)) {
741
895
  return true;
742
896
  }
743
- return event.source instanceof Window && event.origin === connectOrigin && event.source !== iframeWindow;
897
+ return isMessageWindow(event.source) && event.origin === connectOrigin && event.source !== iframeWindow;
898
+ }
899
+ function canDeliverAuthToConnectIframe(iframe) {
900
+ var _a;
901
+ const iframeWindow = (_a = iframe == null ? void 0 : iframe.contentWindow) != null ? _a : null;
902
+ if (!iframeWindow) return false;
903
+ try {
904
+ const href = iframeWindow.location.href;
905
+ return Boolean(href) && href !== "about:blank" && href !== "";
906
+ } catch {
907
+ return true;
908
+ }
909
+ }
910
+ function authMessageType(data2) {
911
+ if (!data2 || typeof data2 !== "object") return void 0;
912
+ const type = data2.type;
913
+ return typeof type === "string" ? type : void 0;
914
+ }
915
+ function resolveLoginPartnerWindow(loginPopup, lastVerifiedPopupSource) {
916
+ var _a;
917
+ const candidates = [];
918
+ if (lastVerifiedPopupSource && !lastVerifiedPopupSource.closed) {
919
+ candidates.push(lastVerifiedPopupSource);
920
+ }
921
+ if (loginPopup && !loginPopup.closed && loginPopup !== lastVerifiedPopupSource) {
922
+ candidates.push(loginPopup);
923
+ }
924
+ if (candidates.length === 0) {
925
+ try {
926
+ const named = window.open("", LOGIN_PARTNER_POPUP_NAME);
927
+ if (named && !named.closed) {
928
+ console.debug("[Benji Connect SDK] LOGIN named_reacquire", { success: true });
929
+ candidates.push(named);
930
+ } else {
931
+ console.debug("[Benji Connect SDK] LOGIN named_reacquire", { success: false });
932
+ }
933
+ } catch {
934
+ console.debug("[Benji Connect SDK] LOGIN named_reacquire", { success: false });
935
+ }
936
+ }
937
+ return (_a = candidates[0]) != null ? _a : null;
938
+ }
939
+ function describeLoginPartnerWindowTarget(target, loginPopup, lastVerifiedPopupSource) {
940
+ if (!target) return "none";
941
+ if (target === lastVerifiedPopupSource) return "lastVerified";
942
+ if (target === loginPopup) return "loginPopup";
943
+ return "named";
744
944
  }
745
945
  function isConnectMessageType(data2) {
746
946
  if (!data2 || typeof data2 !== "object") return false;
@@ -861,6 +1061,8 @@ var ConnectSDK = (() => {
861
1061
  };
862
1062
 
863
1063
  // src/router/message.ts
1064
+ var IFRAME_DELIVERABILITY_POLL_MS = 100;
1065
+ var HOST_AUTH_REDELIVERY_MS = 500;
864
1066
  var _MessageRouter = class _MessageRouter {
865
1067
  static configureMessageRouter(config) {
866
1068
  this.onSuccess = config.onSuccess;
@@ -876,8 +1078,11 @@ var ConnectSDK = (() => {
876
1078
  this.loginPopup = context.loginPopup;
877
1079
  this.mode = context.mode;
878
1080
  this.connectOrigin = new URL(Endpoints.benji_connect_auth_url).origin;
879
- if (this.isIframeReady) {
1081
+ if (this.canDeliverToConnectIframe()) {
1082
+ this.flushPendingHostAuth();
880
1083
  this.flushPendingPopupAuth();
1084
+ } else {
1085
+ this.startIframeDeliverabilityPoll();
881
1086
  }
882
1087
  }
883
1088
  static resetIframeReady() {
@@ -885,6 +1090,9 @@ var ConnectSDK = (() => {
885
1090
  }
886
1091
  static setMode(mode) {
887
1092
  this.mode = mode;
1093
+ if (mode !== 5 /* LOGIN */) {
1094
+ this.stopIframeDeliverabilityPoll();
1095
+ }
888
1096
  }
889
1097
  static addEventListeners() {
890
1098
  if (this.configuredListeners) return;
@@ -897,6 +1105,8 @@ var ConnectSDK = (() => {
897
1105
  window.removeEventListener("error", this.errorEventHandler);
898
1106
  window.removeEventListener("unhandledrejection", this.errorEventHandler);
899
1107
  window.removeEventListener("message", this.messageEventHandler);
1108
+ this.stopIframeDeliverabilityPoll();
1109
+ this.stopHostAuthRedelivery();
900
1110
  this.configuredListeners = false;
901
1111
  this.iframe = null;
902
1112
  this.loginPopup = null;
@@ -904,6 +1114,8 @@ var ConnectSDK = (() => {
904
1114
  this.connectOrigin = "";
905
1115
  this.isIframeReady = false;
906
1116
  this.pendingPopupAuth = [];
1117
+ this.pendingHostAuth = [];
1118
+ this.lastVerifiedPopupSource = null;
907
1119
  }
908
1120
  static handleErrorMessage(event) {
909
1121
  switch (event.type) {
@@ -923,29 +1135,143 @@ var ConnectSDK = (() => {
923
1135
  }
924
1136
  }
925
1137
  }
926
- // Forward popup/tab auth payloads into Connect. Queue until the iframe has loaded;
927
- // otherwise fast SSO (Safari or already-authenticated) can arrive before contentWindow is usable.
1138
+ static canDeliverToConnectIframe() {
1139
+ return canDeliverAuthToConnectIframe(this.iframe);
1140
+ }
1141
+ static startIframeDeliverabilityPoll() {
1142
+ if (this.iframeDeliverabilityPoll !== null || this.mode !== 5 /* LOGIN */ || this.pendingPopupAuth.length === 0) {
1143
+ return;
1144
+ }
1145
+ this.iframeDeliverabilityPoll = window.setInterval(() => {
1146
+ if (this.mode !== 5 /* LOGIN */ || this.pendingPopupAuth.length === 0) {
1147
+ this.stopIframeDeliverabilityPoll();
1148
+ return;
1149
+ }
1150
+ if (this.canDeliverToConnectIframe()) {
1151
+ this.flushPendingPopupAuth();
1152
+ }
1153
+ }, IFRAME_DELIVERABILITY_POLL_MS);
1154
+ }
1155
+ static stopIframeDeliverabilityPoll() {
1156
+ if (this.iframeDeliverabilityPoll === null) return;
1157
+ window.clearInterval(this.iframeDeliverabilityPoll);
1158
+ this.iframeDeliverabilityPoll = null;
1159
+ }
1160
+ // Forward popup/tab auth payloads into Connect. Queue until deliverable.
928
1161
  static relayToIframe(event) {
929
1162
  var _a;
1163
+ const type = authMessageType(event.data);
1164
+ const deliverable = this.canDeliverToConnectIframe();
930
1165
  const iframeWindow = (_a = this.iframe) == null ? void 0 : _a.contentWindow;
931
- if (!this.isIframeReady || !iframeWindow) {
1166
+ if (!iframeWindow || !deliverable) {
932
1167
  this.pendingPopupAuth.push(event);
1168
+ this.startIframeDeliverabilityPoll();
1169
+ console.debug("[Benji Connect SDK] LOGIN relayToIframe queued", {
1170
+ type,
1171
+ queued: true,
1172
+ pendingCount: this.pendingPopupAuth.length,
1173
+ isIframeReady: this.isIframeReady,
1174
+ deliverable
1175
+ });
933
1176
  return;
934
1177
  }
1178
+ if (this.pendingPopupAuth.length > 0) {
1179
+ this.flushPendingPopupAuth();
1180
+ }
935
1181
  iframeWindow.postMessage(event.data, this.connectOrigin);
1182
+ console.debug("[Benji Connect SDK] LOGIN relayToIframe posted", {
1183
+ type,
1184
+ queued: false,
1185
+ pendingCount: this.pendingPopupAuth.length,
1186
+ isIframeReady: this.isIframeReady,
1187
+ deliverable
1188
+ });
1189
+ }
1190
+ /**
1191
+ * Same-tab LOGIN resume: post immediately, then re-post on an interval until
1192
+ * FLOW_SUCCESS / onError / close stops redelivery. Connect dedupes repeats.
1193
+ */
1194
+ static startHostAuthRedelivery(payload, intervalMs = HOST_AUTH_REDELIVERY_MS) {
1195
+ this.stopHostAuthRedelivery();
1196
+ this.upsertPendingHostAuth(payload);
1197
+ this.flushPendingHostAuth();
1198
+ this.hostAuthRedeliveryId = window.setInterval(() => {
1199
+ if (!this.pendingHostAuth.length) {
1200
+ this.stopHostAuthRedelivery();
1201
+ return;
1202
+ }
1203
+ this.flushPendingHostAuth();
1204
+ }, intervalMs);
1205
+ }
1206
+ static stopHostAuthRedelivery() {
1207
+ if (this.hostAuthRedeliveryId !== null) {
1208
+ window.clearInterval(this.hostAuthRedeliveryId);
1209
+ this.hostAuthRedeliveryId = null;
1210
+ }
1211
+ this.pendingHostAuth = [];
1212
+ }
1213
+ static upsertPendingHostAuth(payload) {
1214
+ const type = authMessageType(payload);
1215
+ if (!type) {
1216
+ this.pendingHostAuth.push(payload);
1217
+ return;
1218
+ }
1219
+ const existingIndex = this.pendingHostAuth.findIndex(
1220
+ (item) => authMessageType(item) === type
1221
+ );
1222
+ if (existingIndex >= 0) {
1223
+ this.pendingHostAuth[existingIndex] = payload;
1224
+ } else {
1225
+ this.pendingHostAuth.push(payload);
1226
+ }
1227
+ }
1228
+ static flushPendingHostAuth() {
1229
+ var _a, _b;
1230
+ if (!this.pendingHostAuth.length) return;
1231
+ const iframeWindow = (_b = (_a = this.iframe) == null ? void 0 : _a.contentWindow) != null ? _b : null;
1232
+ if (!iframeWindow || !this.canDeliverToConnectIframe()) {
1233
+ return;
1234
+ }
1235
+ const queued = [...this.pendingHostAuth];
1236
+ for (const payload of queued) {
1237
+ iframeWindow.postMessage(payload, this.connectOrigin);
1238
+ }
1239
+ console.debug("[Benji Connect SDK] LOGIN flushPendingHostAuth", {
1240
+ flushedCount: queued.length
1241
+ });
936
1242
  }
937
1243
  static markIframeReady(iframe) {
938
1244
  if (this.iframe !== iframe) return;
939
1245
  this.isIframeReady = true;
1246
+ console.debug("[Benji Connect SDK] LOGIN iframe_load");
1247
+ this.flushPendingHostAuth();
940
1248
  this.flushPendingPopupAuth();
941
1249
  }
942
1250
  static flushPendingPopupAuth() {
943
1251
  var _a, _b;
944
- if (!this.isIframeReady || !this.pendingPopupAuth.length) return;
945
- const expectedOrigin = this.connectOrigin || new URL(Endpoints.benji_connect_auth_url).origin;
946
1252
  const iframeWindow = (_b = (_a = this.iframe) == null ? void 0 : _a.contentWindow) != null ? _b : null;
947
- const queued = this.pendingPopupAuth;
948
- this.pendingPopupAuth = [];
1253
+ if (!this.pendingPopupAuth.length) {
1254
+ this.stopIframeDeliverabilityPoll();
1255
+ return;
1256
+ }
1257
+ if (!iframeWindow) {
1258
+ this.startIframeDeliverabilityPoll();
1259
+ return;
1260
+ }
1261
+ if (!this.canDeliverToConnectIframe()) {
1262
+ this.startIframeDeliverabilityPoll();
1263
+ console.debug("[Benji Connect SDK] LOGIN flushPendingPopupAuth skipped", {
1264
+ pendingCount: this.pendingPopupAuth.length,
1265
+ isIframeReady: this.isIframeReady,
1266
+ deliverable: false
1267
+ });
1268
+ return;
1269
+ }
1270
+ const expectedOrigin = this.connectOrigin || new URL(Endpoints.benji_connect_auth_url).origin;
1271
+ const queued = [...this.pendingPopupAuth];
1272
+ let flushedCount = 0;
1273
+ let skippedNotOriginated = 0;
1274
+ const remaining = [];
949
1275
  for (const event of queued) {
950
1276
  if (!isPopupOriginatedAuth(
951
1277
  event,
@@ -953,18 +1279,58 @@ var ConnectSDK = (() => {
953
1279
  iframeWindow,
954
1280
  expectedOrigin
955
1281
  )) {
1282
+ skippedNotOriginated += 1;
1283
+ continue;
1284
+ }
1285
+ if (!this.canDeliverToConnectIframe()) {
1286
+ remaining.push(event);
956
1287
  continue;
957
1288
  }
958
- this.relayToIframe(event);
1289
+ iframeWindow.postMessage(event.data, this.connectOrigin);
1290
+ flushedCount += 1;
959
1291
  }
1292
+ this.pendingPopupAuth = remaining;
1293
+ if (this.pendingPopupAuth.length === 0) {
1294
+ this.stopIframeDeliverabilityPoll();
1295
+ } else {
1296
+ this.startIframeDeliverabilityPoll();
1297
+ }
1298
+ console.debug("[Benji Connect SDK] LOGIN flushPendingPopupAuth", {
1299
+ pendingCount: queued.length,
1300
+ flushedCount,
1301
+ skippedNotOriginated,
1302
+ remainingCount: this.pendingPopupAuth.length
1303
+ });
960
1304
  }
961
1305
  static relayToPopup(payload) {
962
- if (!this.loginPopup) {
963
- console.warn("[Benji Connect SDK] Skipping login auth relay, no login popup");
1306
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1307
+ const type = authMessageType(payload);
1308
+ const target = resolveLoginPartnerWindow(
1309
+ this.loginPopup,
1310
+ this.lastVerifiedPopupSource
1311
+ );
1312
+ const targetLabel = describeLoginPartnerWindowTarget(
1313
+ target,
1314
+ this.loginPopup,
1315
+ this.lastVerifiedPopupSource
1316
+ );
1317
+ if (!target) {
1318
+ console.warn("[Benji Connect SDK] Skipping login auth relay, no live partner window", {
1319
+ type,
1320
+ target: targetLabel,
1321
+ loginPopupClosed: (_b = (_a = this.loginPopup) == null ? void 0 : _a.closed) != null ? _b : true,
1322
+ lastVerifiedClosed: (_d = (_c = this.lastVerifiedPopupSource) == null ? void 0 : _c.closed) != null ? _d : true
1323
+ });
964
1324
  return;
965
1325
  }
1326
+ console.debug("[Benji Connect SDK] LOGIN relayToPopup", {
1327
+ type,
1328
+ target: targetLabel,
1329
+ loginPopupClosed: (_f = (_e = this.loginPopup) == null ? void 0 : _e.closed) != null ? _f : true,
1330
+ lastVerifiedClosed: (_h = (_g = this.lastVerifiedPopupSource) == null ? void 0 : _g.closed) != null ? _h : true
1331
+ });
966
1332
  try {
967
- this.loginPopup.postMessage(payload, "*");
1333
+ target.postMessage(payload, "*");
968
1334
  } catch (error) {
969
1335
  console.error("[Benji Connect SDK] Failed to forward login auth message to popup", error);
970
1336
  }
@@ -974,6 +1340,7 @@ var ConnectSDK = (() => {
974
1340
  if (shouldRelayMessage(this.mode, event.data)) {
975
1341
  const expectedOrigin2 = this.connectOrigin || new URL(Endpoints.benji_connect_auth_url).origin;
976
1342
  const iframeWindow = (_a = this.iframe) == null ? void 0 : _a.contentWindow;
1343
+ const type = authMessageType(event.data);
977
1344
  if (iframeWindow && event.source === iframeWindow) {
978
1345
  if (event.origin !== expectedOrigin2) {
979
1346
  console.warn(
@@ -983,13 +1350,30 @@ var ConnectSDK = (() => {
983
1350
  );
984
1351
  return;
985
1352
  }
1353
+ console.debug("[Benji Connect SDK] LOGIN auth relay", {
1354
+ type,
1355
+ origin: event.origin,
1356
+ direction: "iframe_to_popup"
1357
+ });
986
1358
  this.relayToPopup(event.data);
987
1359
  return;
988
1360
  }
989
1361
  if (isPopupOriginatedAuth(event, this.loginPopup, iframeWindow != null ? iframeWindow : null, expectedOrigin2)) {
1362
+ if (isMessageWindow(event.source)) {
1363
+ this.lastVerifiedPopupSource = event.source;
1364
+ }
1365
+ console.debug("[Benji Connect SDK] LOGIN auth relay", {
1366
+ type,
1367
+ origin: event.origin,
1368
+ direction: "popup_to_iframe"
1369
+ });
990
1370
  this.relayToIframe(event);
991
1371
  return;
992
1372
  }
1373
+ console.warn("[Benji Connect SDK] LOGIN auth relay unclassified", {
1374
+ type,
1375
+ origin: event.origin
1376
+ });
993
1377
  return;
994
1378
  }
995
1379
  if (!isConnectMessageType(event.data)) return;
@@ -1066,6 +1450,18 @@ var ConnectSDK = (() => {
1066
1450
  _MessageRouter.connectOrigin = "";
1067
1451
  _MessageRouter.isIframeReady = false;
1068
1452
  _MessageRouter.pendingPopupAuth = [];
1453
+ /**
1454
+ * Sticky host→iframe auth (same-tab LOGIN resume). Kept after each post so
1455
+ * redelivery can cover the window where the iframe is "deliverable" but
1456
+ * Connect has not installed its message listener yet.
1457
+ * Invariant: only `startHostAuthRedelivery` writes this queue, and it always
1458
+ * owns a timer while the queue is non-empty. The deliverability poll is
1459
+ * popup-only so it cannot double-post this payload.
1460
+ */
1461
+ _MessageRouter.pendingHostAuth = [];
1462
+ _MessageRouter.hostAuthRedeliveryId = null;
1463
+ _MessageRouter.lastVerifiedPopupSource = null;
1464
+ _MessageRouter.iframeDeliverabilityPoll = null;
1069
1465
  _MessageRouter.errorEventHandler = (event) => {
1070
1466
  return _MessageRouter.handleErrorMessage(event);
1071
1467
  };
@@ -1076,6 +1472,7 @@ var ConnectSDK = (() => {
1076
1472
 
1077
1473
  // src/connect-sdk.ts
1078
1474
  var POPUP_CLOSED_POLL_MS = 500;
1475
+ var SAME_TAB_RESUME_TIMEOUT_MS = 45e3;
1079
1476
  var ConnectSDK = class {
1080
1477
  constructor(config) {
1081
1478
  this.iframe = null;
@@ -1105,6 +1502,9 @@ var ConnectSDK = (() => {
1105
1502
  * while the in-flight open() still mounts the iframe.
1106
1503
  */
1107
1504
  this.openAbortController = null;
1505
+ /** Same-tab LOGIN resume mounts the iframe headless without partner auth type. */
1506
+ this.isResumingSameTabLogin = false;
1507
+ this.sameTabResumeTimeoutId = null;
1108
1508
  this.onIframeLoad = () => {
1109
1509
  if (this.isClosing || !this.iframe) return;
1110
1510
  MessageRouter.markIframeReady(this.iframe);
@@ -1122,9 +1522,64 @@ var ConnectSDK = (() => {
1122
1522
  }
1123
1523
  isLoginPopupOnly() {
1124
1524
  var _a;
1125
- const mode = (_a = this.sdkConfig.mode) != null ? _a : 1 /* CONNECT */;
1126
- const authType = this.resolveAuthType();
1127
- return mode === 5 /* LOGIN */ && authType != null && authType !== 0 /* DUMMY */;
1525
+ return isNonDummyLogin(
1526
+ (_a = this.sdkConfig.mode) != null ? _a : 1 /* CONNECT */,
1527
+ this.resolveAuthType()
1528
+ );
1529
+ }
1530
+ shouldOpenSameTabLogin() {
1531
+ var _a, _b, _c;
1532
+ if (!this.isLoginPopupOnly()) return false;
1533
+ const partnerAuthLink = (_a = this.sdkConfig.tokenData) == null ? void 0 : _a.partnerAuthLink;
1534
+ const configToken = (_b = this.sdkConfig.tokenData) == null ? void 0 : _b.configToken;
1535
+ if (!partnerAuthLink || !configToken || !canStampLoginReturnState(partnerAuthLink, configToken)) {
1536
+ return false;
1537
+ }
1538
+ const storage = typeof sessionStorage === "undefined" ? null : sessionStorage;
1539
+ return shouldUseSameTabLogin({
1540
+ isNonDummyLogin: true,
1541
+ userAgent: typeof navigator === "undefined" ? "" : navigator.userAgent,
1542
+ maxTouchPoints: typeof navigator === "undefined" ? 0 : (_c = navigator.maxTouchPoints) != null ? _c : 0,
1543
+ forceRedirect: isLocalForceRedirectEnabled(Environment, storage)
1544
+ });
1545
+ }
1546
+ openSameTabLogin() {
1547
+ var _a, _b, _c, _d, _e, _f;
1548
+ const partnerAuthLink = (_a = this.sdkConfig.tokenData) == null ? void 0 : _a.partnerAuthLink;
1549
+ if (!partnerAuthLink) {
1550
+ console.warn("[Connect SDK] Same-tab LOGIN missing partnerAuthLink");
1551
+ (_c = (_b = this.sdkConfig).onError) == null ? void 0 : _c.call(
1552
+ _b,
1553
+ new Error("Partner sign-in URL is missing."),
1554
+ "400",
1555
+ { context: buildContext() }
1556
+ );
1557
+ return;
1558
+ }
1559
+ const rewriteForLocalConnect = Environment === "local" /* LOCAL */;
1560
+ const returnOrigin = window.location.origin;
1561
+ const url = buildSameTabPartnerAuthUrl({
1562
+ partnerAuthLink,
1563
+ configToken: (_d = this.sdkConfig.tokenData) == null ? void 0 : _d.configToken,
1564
+ rewriteForLocalConnect,
1565
+ returnOrigin
1566
+ });
1567
+ try {
1568
+ writeLoginPending(sessionStorage, {
1569
+ connectToken: this.sdkConfig.token,
1570
+ path: `${window.location.pathname}${window.location.search}`
1571
+ });
1572
+ } catch (error) {
1573
+ console.warn("[Connect SDK] Failed to persist LOGIN pending state", error);
1574
+ (_f = (_e = this.sdkConfig).onError) == null ? void 0 : _f.call(
1575
+ _e,
1576
+ new Error("Sign-in could not be started."),
1577
+ "400",
1578
+ { context: buildContext() }
1579
+ );
1580
+ return;
1581
+ }
1582
+ window.location.assign(url);
1128
1583
  }
1129
1584
  isSessionActive() {
1130
1585
  return this.iframe != null || this.loginPopup != null;
@@ -1137,8 +1592,22 @@ var ConnectSDK = (() => {
1137
1592
  }
1138
1593
  initialize() {
1139
1594
  MessageRouter.configureMessageRouter({
1140
- onSuccess: this.sdkConfig.onSuccess,
1141
- onError: this.sdkConfig.onError,
1595
+ onSuccess: (token, metadata) => {
1596
+ var _a, _b;
1597
+ this.clearSameTabResumeTimeout();
1598
+ MessageRouter.stopHostAuthRedelivery();
1599
+ (_b = (_a = this.sdkConfig).onSuccess) == null ? void 0 : _b.call(_a, token, metadata);
1600
+ },
1601
+ onError: (error, errorId, metadata) => {
1602
+ var _a, _b;
1603
+ if (this.isResumingSameTabLogin) {
1604
+ this.close();
1605
+ } else {
1606
+ this.clearSameTabResumeTimeout();
1607
+ MessageRouter.stopHostAuthRedelivery();
1608
+ }
1609
+ (_b = (_a = this.sdkConfig).onError) == null ? void 0 : _b.call(_a, error, errorId, metadata);
1610
+ },
1142
1611
  onExit: this.sdkConfig.onExit,
1143
1612
  onEvent: this.sdkConfig.onEvent,
1144
1613
  close: () => this.close(),
@@ -1159,7 +1628,7 @@ var ConnectSDK = (() => {
1159
1628
  const popupUrl = preparedLink ? appendLoginPopupFlag(preparedLink) : "about:blank";
1160
1629
  this.loginPopup = window.open(
1161
1630
  popupUrl,
1162
- "benji_partner_auth",
1631
+ LOGIN_PARTNER_POPUP_NAME,
1163
1632
  buildLoginPopupFeatures(position)
1164
1633
  );
1165
1634
  if (!this.loginPopup) {
@@ -1186,6 +1655,15 @@ var ConnectSDK = (() => {
1186
1655
  var _a, _b, _c;
1187
1656
  if (this.isClosing) return;
1188
1657
  if ((_a = this.loginPopup) == null ? void 0 : _a.closed) {
1658
+ const live = resolveLoginPartnerWindow(
1659
+ this.loginPopup,
1660
+ MessageRouter.lastVerifiedPopupSource
1661
+ );
1662
+ if (live) {
1663
+ console.debug("[Benji Connect SDK] LOGIN popup_closed_poll skipped, live partner window remains");
1664
+ return;
1665
+ }
1666
+ console.debug("[Benji Connect SDK] LOGIN popup_closed_poll exit, no live partner window");
1189
1667
  (_c = (_b = this.sdkConfig).onExit) == null ? void 0 : _c.call(_b, {
1190
1668
  context: buildContext(),
1191
1669
  trigger: "POPUP_CLOSED" /* POPUP_CLOSED */
@@ -1223,6 +1701,11 @@ var ConnectSDK = (() => {
1223
1701
  const abortController = new AbortController();
1224
1702
  this.openAbortController = abortController;
1225
1703
  const { signal } = abortController;
1704
+ if (this.shouldOpenSameTabLogin()) {
1705
+ this.openSameTabLogin();
1706
+ this.abortInflightOpen();
1707
+ return;
1708
+ }
1226
1709
  this.initialize();
1227
1710
  if (signal.aborted) return;
1228
1711
  MessageRouter.setMode(mode);
@@ -1243,6 +1726,55 @@ var ConnectSDK = (() => {
1243
1726
  this.openUI();
1244
1727
  this.syncLoginContext(mode);
1245
1728
  }
1729
+ /**
1730
+ * Finishes a same-tab LOGIN after the partner redirect returns to the host.
1731
+ * The Connect callback page has no session of its own, so the code exchange
1732
+ * runs in the headless Connect iframe, which authenticates with the connect
1733
+ * token. Completion arrives through the usual FLOW_SUCCESS → onSuccess path.
1734
+ */
1735
+ async resumeSameTabLogin(params) {
1736
+ var _a;
1737
+ if (this.isSessionActive()) return;
1738
+ this.sdkConfig.mode = 5 /* LOGIN */;
1739
+ this.isResumingSameTabLogin = true;
1740
+ this.initialize();
1741
+ MessageRouter.setMode(5 /* LOGIN */);
1742
+ MessageRouter.addEventListeners();
1743
+ this.openUI();
1744
+ this.syncLoginContext(5 /* LOGIN */);
1745
+ const payload = buildRequestCodeExchangeMessage({
1746
+ partnerId: params.partnerId,
1747
+ code: params.code,
1748
+ token: params.state,
1749
+ redirectUri: (_a = params.redirectUri) != null ? _a : this.buildCallbackUrl(params.partnerId)
1750
+ });
1751
+ MessageRouter.startHostAuthRedelivery(payload);
1752
+ this.startSameTabResumeTimeout();
1753
+ }
1754
+ buildCallbackUrl(partnerId) {
1755
+ const origin = new URL(Endpoints.benji_connect_auth_url).origin;
1756
+ return `${origin}/auth/${partnerId}/callback`;
1757
+ }
1758
+ startSameTabResumeTimeout() {
1759
+ this.clearSameTabResumeTimeout();
1760
+ this.sameTabResumeTimeoutId = setTimeout(() => {
1761
+ var _a, _b;
1762
+ this.sameTabResumeTimeoutId = null;
1763
+ console.warn("[Connect SDK] Same-tab LOGIN resume timed out");
1764
+ (_b = (_a = this.sdkConfig).onError) == null ? void 0 : _b.call(
1765
+ _a,
1766
+ new Error("Sign-in could not be completed. Please try again."),
1767
+ "408",
1768
+ { context: buildContext() }
1769
+ );
1770
+ this.close();
1771
+ }, SAME_TAB_RESUME_TIMEOUT_MS);
1772
+ }
1773
+ clearSameTabResumeTimeout() {
1774
+ if (this.sameTabResumeTimeoutId === null) return;
1775
+ clearTimeout(this.sameTabResumeTimeoutId);
1776
+ this.sameTabResumeTimeoutId = null;
1777
+ }
1246
1778
  syncLoginContext(mode) {
1247
1779
  MessageRouter.setLoginContext({
1248
1780
  iframe: this.iframe,
@@ -1254,7 +1786,7 @@ var ConnectSDK = (() => {
1254
1786
  var _a, _b;
1255
1787
  if (this.iframe) return;
1256
1788
  MessageRouter.resetIframeReady();
1257
- const headless = this.isLoginPopupOnly();
1789
+ const headless = this.isResumingSameTabLogin || this.isLoginPopupOnly();
1258
1790
  this.container = document.createElement("div");
1259
1791
  if (headless) {
1260
1792
  applyHeadlessContainerStyle(this.container);
@@ -1270,6 +1802,9 @@ var ConnectSDK = (() => {
1270
1802
  if (this.sdkConfig.mode != null) {
1271
1803
  url.searchParams.set("mode", String(this.sdkConfig.mode));
1272
1804
  }
1805
+ if (this.isResumingSameTabLogin) {
1806
+ url.searchParams.set(LOGIN_RESUME_QUERY_FLAG, "1");
1807
+ }
1273
1808
  this.iframe = document.createElement("iframe");
1274
1809
  this.syncLoginContext((_a = this.sdkConfig.mode) != null ? _a : 1 /* CONNECT */);
1275
1810
  this.iframe.addEventListener("load", this.onIframeLoad);
@@ -1316,6 +1851,10 @@ var ConnectSDK = (() => {
1316
1851
  this.syncLoginContext((_b = this.sdkConfig.mode) != null ? _b : 1 /* CONNECT */);
1317
1852
  }
1318
1853
  navigateLoginPopup(url) {
1854
+ if (this.isResumingSameTabLogin) {
1855
+ console.debug("[Connect SDK] Ignoring PARTNER_LOGIN_OPEN during same-tab resume");
1856
+ return;
1857
+ }
1319
1858
  if (this.hasLoginPopupUrl) return;
1320
1859
  if (!this.loginPopup || this.loginPopup.closed) {
1321
1860
  console.warn("[Connect SDK] Cannot navigate LOGIN popup \u2014 window unavailable");
@@ -1344,6 +1883,9 @@ var ConnectSDK = (() => {
1344
1883
  close() {
1345
1884
  var _a, _b;
1346
1885
  this.abortInflightOpen();
1886
+ this.clearSameTabResumeTimeout();
1887
+ MessageRouter.stopHostAuthRedelivery();
1888
+ this.isResumingSameTabLogin = false;
1347
1889
  this.isClosing = true;
1348
1890
  this.stopPopupClosedPoll();
1349
1891
  this.closeLoginPopup();