@benji-money/connect-sdk 1.1.4-beta.1 → 1.1.4-beta.3
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 +3 -2
- package/dist/connect-sdk.umd.global.js +88 -25
- package/dist/connect-sdk.umd.global.js.map +1 -1
- package/dist/index.cjs +88 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -5
- package/dist/index.d.ts +8 -5
- package/dist/index.js +88 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,7 +88,7 @@ The SDK accepts the following configuration options:
|
|
|
88
88
|
- `token` (required): Your API connect token
|
|
89
89
|
- `mode` (optional): `BenjiConnectMode` — `CONNECT` (default), `TRANSFER`, `REDEEM`, or `LOGIN`
|
|
90
90
|
- `authType` (optional): `PartnerIntegrationType` — LOGIN only; omit or set `DUMMY` for the visible modal iframe; set to your partner type (1–6) for popup-only auth. Prefer `tokenData.authType` when using `tokenData`.
|
|
91
|
-
- `tokenData` (optional): `BenjiConnectTokenData` — LOGIN only; pass `partnerAuthLink` (Auth Service `connect_url`) and `authType` so the SDK opens the partner auth URL synchronously
|
|
91
|
+
- `tokenData` (optional): `BenjiConnectTokenData` — LOGIN only; pass `partnerAuthLink` (Auth Service `connect_url`), `configToken` (mint `config_token`, used as OAuth `state` on the popup URL), and `authType` so the SDK opens the partner auth URL synchronously on `open()`
|
|
92
92
|
- `onSuccess` (optional): Callback function called when connect completed successfully
|
|
93
93
|
- `onError` (optional): Callback function called when error occurs in the connect flow
|
|
94
94
|
- `onExit` (optional): Callback function called when the user exits the connect flow
|
|
@@ -164,6 +164,7 @@ const sdk = new ConnectSDK({
|
|
|
164
164
|
tokenData: {
|
|
165
165
|
authType: PartnerIntegrationType.OAUTH_PKCE,
|
|
166
166
|
partnerAuthLink: connectUrl, // from Auth Service connect_url
|
|
167
|
+
configToken: configToken, // from GET /connect/token — OAuth state
|
|
167
168
|
},
|
|
168
169
|
anchor: "#login-btn",
|
|
169
170
|
placement: BenjiConnectPlacement.BOTTOM_END,
|
|
@@ -176,7 +177,7 @@ const sdk = new ConnectSDK({
|
|
|
176
177
|
await sdk.open();
|
|
177
178
|
```
|
|
178
179
|
|
|
179
|
-
> **`bottom-end`** positions the **partner popup** below the anchor with its right edge aligned to the anchor's right edge. The Benji Connect iframe stays headless (not visible). When `tokenData.partnerAuthLink` is set, the popup
|
|
180
|
+
> **`bottom-end`** positions the **partner popup** below the anchor with its right edge aligned to the anchor's right edge. The Benji Connect iframe stays headless (not visible). When `tokenData.partnerAuthLink` is set, the popup opens that URL on `open()` (with `state` from `configToken` when provided) after LOGIN listeners and popup context are registered. The SDK skips `PARTNER_LOGIN_OPEN` navigation when the popup already has a partner URL. Popup→iframe auth messages are queued until the Connect iframe `load` event. Otherwise the pre-opened popup shows generic loading UI (`Loading sign-in…`) until Benji Connect sends the partner auth URL.
|
|
180
181
|
|
|
181
182
|
### Environments
|
|
182
183
|
|
|
@@ -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.
|
|
113
|
+
version: "1.1.4-beta.3" ? String("1.1.4-beta.3") : "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 */;
|
|
@@ -217,10 +217,34 @@ var ConnectSDK = (() => {
|
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
// src/utils/partnerAuthLink.ts
|
|
220
|
+
var LOGIN_POPUP_QUERY_FLAG = "benji_login_popup";
|
|
221
|
+
function ensureAuthLinkStateNonce(url, nonce) {
|
|
222
|
+
try {
|
|
223
|
+
const parsed = new URL(url);
|
|
224
|
+
if (!parsed.searchParams.get("state")) {
|
|
225
|
+
parsed.searchParams.set("state", nonce);
|
|
226
|
+
}
|
|
227
|
+
return parsed.toString();
|
|
228
|
+
} catch {
|
|
229
|
+
if (url.includes("state=")) {
|
|
230
|
+
return url;
|
|
231
|
+
}
|
|
232
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
233
|
+
return `${url}${separator}state=${encodeURIComponent(nonce)}`;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
220
236
|
function appendLoginPopupFlag(url) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
237
|
+
try {
|
|
238
|
+
const parsed = new URL(url);
|
|
239
|
+
parsed.searchParams.set(LOGIN_POPUP_QUERY_FLAG, "1");
|
|
240
|
+
return parsed.toString();
|
|
241
|
+
} catch {
|
|
242
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
243
|
+
return `${url}${separator}${LOGIN_POPUP_QUERY_FLAG}=1`;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function preparePartnerAuthUrl(url, nonce) {
|
|
247
|
+
return ensureAuthLinkStateNonce(url, nonce);
|
|
224
248
|
}
|
|
225
249
|
|
|
226
250
|
// src/utils/placement.ts
|
|
@@ -833,6 +857,12 @@ var ConnectSDK = (() => {
|
|
|
833
857
|
this.loginPopup = context.loginPopup;
|
|
834
858
|
this.mode = context.mode;
|
|
835
859
|
this.connectOrigin = new URL(Endpoints.benji_connect_auth_url).origin;
|
|
860
|
+
if (this.isIframeReady) {
|
|
861
|
+
this.flushPendingPopupAuth();
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
static resetIframeReady() {
|
|
865
|
+
this.isIframeReady = false;
|
|
836
866
|
}
|
|
837
867
|
static setMode(mode) {
|
|
838
868
|
this.mode = mode;
|
|
@@ -853,6 +883,8 @@ var ConnectSDK = (() => {
|
|
|
853
883
|
this.loginPopup = null;
|
|
854
884
|
this.mode = 1 /* CONNECT */;
|
|
855
885
|
this.connectOrigin = "";
|
|
886
|
+
this.isIframeReady = false;
|
|
887
|
+
this.pendingPopupAuth = [];
|
|
856
888
|
}
|
|
857
889
|
static handleErrorMessage(event) {
|
|
858
890
|
switch (event.type) {
|
|
@@ -875,12 +907,25 @@ var ConnectSDK = (() => {
|
|
|
875
907
|
static relayToIframe(event) {
|
|
876
908
|
var _a;
|
|
877
909
|
const iframeWindow = (_a = this.iframe) == null ? void 0 : _a.contentWindow;
|
|
878
|
-
if (!iframeWindow) {
|
|
879
|
-
|
|
910
|
+
if (!this.isIframeReady || !iframeWindow) {
|
|
911
|
+
this.pendingPopupAuth.push(event);
|
|
880
912
|
return;
|
|
881
913
|
}
|
|
882
914
|
iframeWindow.postMessage(event.data, this.connectOrigin);
|
|
883
915
|
}
|
|
916
|
+
static markIframeReady(iframe) {
|
|
917
|
+
if (this.iframe !== iframe) return;
|
|
918
|
+
this.isIframeReady = true;
|
|
919
|
+
this.flushPendingPopupAuth();
|
|
920
|
+
}
|
|
921
|
+
static flushPendingPopupAuth() {
|
|
922
|
+
if (!this.isIframeReady || !this.pendingPopupAuth.length) return;
|
|
923
|
+
const queued = this.pendingPopupAuth;
|
|
924
|
+
this.pendingPopupAuth = [];
|
|
925
|
+
for (const event of queued) {
|
|
926
|
+
this.relayToIframe(event);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
884
929
|
static relayToPopup(payload) {
|
|
885
930
|
if (!this.loginPopup) {
|
|
886
931
|
console.warn("[Benji Connect SDK] Skipping login auth relay, no login popup");
|
|
@@ -894,13 +939,17 @@ var ConnectSDK = (() => {
|
|
|
894
939
|
}
|
|
895
940
|
static handleMessage(event) {
|
|
896
941
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
897
|
-
if (shouldRelayMessage(this.mode, event.data)
|
|
942
|
+
if (shouldRelayMessage(this.mode, event.data)) {
|
|
898
943
|
const expectedOrigin2 = this.connectOrigin || new URL(Endpoints.benji_connect_auth_url).origin;
|
|
899
944
|
const iframeWindow = (_a = this.iframe) == null ? void 0 : _a.contentWindow;
|
|
900
|
-
if (event.source === this.loginPopup) {
|
|
945
|
+
if (this.loginPopup && event.source === this.loginPopup) {
|
|
901
946
|
this.relayToIframe(event);
|
|
902
947
|
return;
|
|
903
948
|
}
|
|
949
|
+
if (!this.loginPopup && event.source instanceof Window && event.source !== iframeWindow) {
|
|
950
|
+
this.pendingPopupAuth.push(event);
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
904
953
|
if (iframeWindow && event.source === iframeWindow) {
|
|
905
954
|
if (event.origin !== expectedOrigin2) {
|
|
906
955
|
console.warn(
|
|
@@ -987,6 +1036,8 @@ var ConnectSDK = (() => {
|
|
|
987
1036
|
_MessageRouter.loginPopup = null;
|
|
988
1037
|
_MessageRouter.mode = 1 /* CONNECT */;
|
|
989
1038
|
_MessageRouter.connectOrigin = "";
|
|
1039
|
+
_MessageRouter.isIframeReady = false;
|
|
1040
|
+
_MessageRouter.pendingPopupAuth = [];
|
|
990
1041
|
_MessageRouter.errorEventHandler = (event) => {
|
|
991
1042
|
return _MessageRouter.handleErrorMessage(event);
|
|
992
1043
|
};
|
|
@@ -1021,11 +1072,15 @@ var ConnectSDK = (() => {
|
|
|
1021
1072
|
this.isClosing = false;
|
|
1022
1073
|
/**
|
|
1023
1074
|
* Aborts a pending open() when close() runs before the iframe mounts.
|
|
1024
|
-
* Non-DUMMY LOGIN opens the partner popup synchronously,
|
|
1025
|
-
*
|
|
1026
|
-
* while the in-flight open() still
|
|
1075
|
+
* Non-DUMMY LOGIN arms listeners, then opens the partner popup synchronously,
|
|
1076
|
+
* then mounts a headless iframe. Without this, close() can tear down the popup
|
|
1077
|
+
* while the in-flight open() still mounts the iframe.
|
|
1027
1078
|
*/
|
|
1028
1079
|
this.openAbortController = null;
|
|
1080
|
+
this.onIframeLoad = () => {
|
|
1081
|
+
if (this.isClosing || !this.iframe) return;
|
|
1082
|
+
MessageRouter.markIframeReady(this.iframe);
|
|
1083
|
+
};
|
|
1029
1084
|
this.sdkConfig = config;
|
|
1030
1085
|
if (!this.sdkConfig.environment) throw new Error("Environment is required");
|
|
1031
1086
|
if (!this.sdkConfig.token) throw new Error("Connect token is required");
|
|
@@ -1052,7 +1107,7 @@ var ConnectSDK = (() => {
|
|
|
1052
1107
|
(_a = this.openAbortController) == null ? void 0 : _a.abort();
|
|
1053
1108
|
this.openAbortController = null;
|
|
1054
1109
|
}
|
|
1055
|
-
|
|
1110
|
+
initialize() {
|
|
1056
1111
|
MessageRouter.configureMessageRouter({
|
|
1057
1112
|
onSuccess: this.sdkConfig.onSuccess,
|
|
1058
1113
|
onError: this.sdkConfig.onError,
|
|
@@ -1064,14 +1119,16 @@ var ConnectSDK = (() => {
|
|
|
1064
1119
|
});
|
|
1065
1120
|
}
|
|
1066
1121
|
openLoginPopup() {
|
|
1067
|
-
var _a, _b, _c;
|
|
1122
|
+
var _a, _b, _c, _d;
|
|
1068
1123
|
const position = computePopupScreenPosition(
|
|
1069
1124
|
this.sdkConfig,
|
|
1070
1125
|
this.popupLayoutState,
|
|
1071
1126
|
{ warnOnInvalidAnchor: true }
|
|
1072
1127
|
);
|
|
1073
1128
|
const partnerAuthLink = (_a = this.sdkConfig.tokenData) == null ? void 0 : _a.partnerAuthLink;
|
|
1074
|
-
const
|
|
1129
|
+
const configToken = (_b = this.sdkConfig.tokenData) == null ? void 0 : _b.configToken;
|
|
1130
|
+
const preparedLink = partnerAuthLink && configToken ? preparePartnerAuthUrl(partnerAuthLink, configToken) : partnerAuthLink;
|
|
1131
|
+
const popupUrl = preparedLink ? appendLoginPopupFlag(preparedLink) : "about:blank";
|
|
1075
1132
|
this.loginPopup = window.open(
|
|
1076
1133
|
popupUrl,
|
|
1077
1134
|
"benji_partner_auth",
|
|
@@ -1079,15 +1136,15 @@ var ConnectSDK = (() => {
|
|
|
1079
1136
|
);
|
|
1080
1137
|
if (!this.loginPopup) {
|
|
1081
1138
|
console.warn("[Connect SDK] Failed to pre-open LOGIN partner auth popup");
|
|
1082
|
-
(
|
|
1083
|
-
|
|
1139
|
+
(_d = (_c = this.sdkConfig).onError) == null ? void 0 : _d.call(
|
|
1140
|
+
_c,
|
|
1084
1141
|
new Error("Popup blocked. Allow popups for this site and try again."),
|
|
1085
1142
|
"400",
|
|
1086
1143
|
{ context: buildContext() }
|
|
1087
1144
|
);
|
|
1088
1145
|
return false;
|
|
1089
1146
|
}
|
|
1090
|
-
if (
|
|
1147
|
+
if (preparedLink) {
|
|
1091
1148
|
this.hasLoginPopupUrl = true;
|
|
1092
1149
|
} else {
|
|
1093
1150
|
writeLoginPopupLoadingDocument(this.loginPopup);
|
|
@@ -1138,17 +1195,19 @@ var ConnectSDK = (() => {
|
|
|
1138
1195
|
const abortController = new AbortController();
|
|
1139
1196
|
this.openAbortController = abortController;
|
|
1140
1197
|
const { signal } = abortController;
|
|
1198
|
+
this.initialize();
|
|
1199
|
+
if (signal.aborted) return;
|
|
1200
|
+
MessageRouter.setMode(mode);
|
|
1201
|
+
MessageRouter.addEventListeners();
|
|
1141
1202
|
if (this.isLoginPopupOnly()) {
|
|
1142
1203
|
const opened = this.openLoginPopup();
|
|
1143
1204
|
if (!opened) {
|
|
1144
1205
|
this.abortInflightOpen();
|
|
1206
|
+
MessageRouter.removeEventListeners();
|
|
1145
1207
|
return;
|
|
1146
1208
|
}
|
|
1209
|
+
this.syncLoginContext(mode);
|
|
1147
1210
|
}
|
|
1148
|
-
await this.initialize();
|
|
1149
|
-
if (signal.aborted) return;
|
|
1150
|
-
MessageRouter.setMode(mode);
|
|
1151
|
-
MessageRouter.addEventListeners();
|
|
1152
1211
|
if (signal.aborted) {
|
|
1153
1212
|
MessageRouter.removeEventListeners();
|
|
1154
1213
|
return;
|
|
@@ -1164,8 +1223,9 @@ var ConnectSDK = (() => {
|
|
|
1164
1223
|
});
|
|
1165
1224
|
}
|
|
1166
1225
|
openUI() {
|
|
1167
|
-
var _a;
|
|
1226
|
+
var _a, _b;
|
|
1168
1227
|
if (this.iframe) return;
|
|
1228
|
+
MessageRouter.resetIframeReady();
|
|
1169
1229
|
const headless = this.isLoginPopupOnly();
|
|
1170
1230
|
this.container = document.createElement("div");
|
|
1171
1231
|
if (headless) {
|
|
@@ -1183,6 +1243,8 @@ var ConnectSDK = (() => {
|
|
|
1183
1243
|
url.searchParams.set("mode", String(this.sdkConfig.mode));
|
|
1184
1244
|
}
|
|
1185
1245
|
this.iframe = document.createElement("iframe");
|
|
1246
|
+
this.syncLoginContext((_a = this.sdkConfig.mode) != null ? _a : 1 /* CONNECT */);
|
|
1247
|
+
this.iframe.addEventListener("load", this.onIframeLoad);
|
|
1186
1248
|
this.iframe.src = url.toString();
|
|
1187
1249
|
this.container.appendChild(this.iframe);
|
|
1188
1250
|
document.body.appendChild(this.container);
|
|
@@ -1213,9 +1275,9 @@ var ConnectSDK = (() => {
|
|
|
1213
1275
|
}
|
|
1214
1276
|
);
|
|
1215
1277
|
this.container.addEventListener("click", (e) => {
|
|
1216
|
-
var _a2,
|
|
1278
|
+
var _a2, _b2;
|
|
1217
1279
|
if (e.target === this.container) {
|
|
1218
|
-
(
|
|
1280
|
+
(_b2 = (_a2 = this.sdkConfig).onExit) == null ? void 0 : _b2.call(_a2, {
|
|
1219
1281
|
context: buildContext(),
|
|
1220
1282
|
trigger: "TAPPED_OUT_OF_BOUNDS" /* TAPPED_OUT_OF_BOUNDS */
|
|
1221
1283
|
});
|
|
@@ -1223,7 +1285,7 @@ var ConnectSDK = (() => {
|
|
|
1223
1285
|
}
|
|
1224
1286
|
});
|
|
1225
1287
|
}
|
|
1226
|
-
this.syncLoginContext((
|
|
1288
|
+
this.syncLoginContext((_b = this.sdkConfig.mode) != null ? _b : 1 /* CONNECT */);
|
|
1227
1289
|
}
|
|
1228
1290
|
navigateLoginPopup(url) {
|
|
1229
1291
|
if (this.hasLoginPopupUrl) return;
|
|
@@ -1266,6 +1328,7 @@ var ConnectSDK = (() => {
|
|
|
1266
1328
|
this.repositionListeners = null;
|
|
1267
1329
|
this.layoutState.lastValidAnchoredLayout = null;
|
|
1268
1330
|
this.popupLayoutState.lastValidAnchoredLayout = null;
|
|
1331
|
+
this.iframe.removeEventListener("load", this.onIframeLoad);
|
|
1269
1332
|
if ((_b = this.container) == null ? void 0 : _b.parentNode) {
|
|
1270
1333
|
this.container.parentNode.removeChild(this.container);
|
|
1271
1334
|
}
|