@benji-money/connect-sdk 1.1.3 → 1.1.4-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 +9 -4
- package/dist/connect-sdk.umd.global.js +92 -13
- package/dist/connect-sdk.umd.global.js.map +1 -1
- package/dist/index.cjs +92 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +92 -13
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -87,7 +87,8 @@ The SDK accepts the following configuration options:
|
|
|
87
87
|
- `environment` (required): One of `local`, `development`, `sandbox`, or `production`
|
|
88
88
|
- `token` (required): Your API connect token
|
|
89
89
|
- `mode` (optional): `BenjiConnectMode` — `CONNECT` (default), `TRANSFER`, `REDEEM`, or `LOGIN`
|
|
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
|
|
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`), `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()`
|
|
91
92
|
- `onSuccess` (optional): Callback function called when connect completed successfully
|
|
92
93
|
- `onError` (optional): Callback function called when error occurs in the connect flow
|
|
93
94
|
- `onExit` (optional): Callback function called when the user exits the connect flow
|
|
@@ -102,7 +103,7 @@ The SDK accepts the following configuration options:
|
|
|
102
103
|
| ---- | ---------- | ----- |
|
|
103
104
|
| **CONNECT** (default) | Centered or anchored modal iframe | No `authType`; standard connect / verify flow |
|
|
104
105
|
| **LOGIN + DUMMY / no `authType`** | Same visible modal iframe | Partner auth runs in a nested iframe inside Benji Connect |
|
|
105
|
-
| **LOGIN + non-DUMMY** | Partner popup only | Pass `authType
|
|
106
|
+
| **LOGIN + non-DUMMY** | Partner popup only | Pass `authType` (or `tokenData.authType`); Benji Connect iframe is headless — popup opens partner auth immediately when `tokenData.partnerAuthLink` is set, otherwise shows loading UI until Benji Connect navigates — see [LOGIN popup example](#login-popup-example) below |
|
|
106
107
|
|
|
107
108
|
### Placement
|
|
108
109
|
|
|
@@ -160,7 +161,11 @@ const sdk = new ConnectSDK({
|
|
|
160
161
|
environment: "sandbox",
|
|
161
162
|
token: connectToken,
|
|
162
163
|
mode: BenjiConnectMode.LOGIN,
|
|
163
|
-
|
|
164
|
+
tokenData: {
|
|
165
|
+
authType: PartnerIntegrationType.OAUTH_PKCE,
|
|
166
|
+
partnerAuthLink: connectUrl, // from Auth Service connect_url
|
|
167
|
+
configToken: configToken, // from GET /connect/token — OAuth state
|
|
168
|
+
},
|
|
164
169
|
anchor: "#login-btn",
|
|
165
170
|
placement: BenjiConnectPlacement.BOTTOM_END,
|
|
166
171
|
offset: { top: 8 },
|
|
@@ -172,7 +177,7 @@ const sdk = new ConnectSDK({
|
|
|
172
177
|
await sdk.open();
|
|
173
178
|
```
|
|
174
179
|
|
|
175
|
-
> **`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). The pre-opened popup shows generic loading UI (`Loading sign-in…`) until Benji Connect sends the partner auth URL.
|
|
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). 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.
|
|
176
181
|
|
|
177
182
|
### Environments
|
|
178
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.
|
|
113
|
+
version: "1.1.4-beta.2" ? String("1.1.4-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 */;
|
|
@@ -216,6 +216,37 @@ var ConnectSDK = (() => {
|
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
218
|
|
|
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
|
+
}
|
|
236
|
+
function appendLoginPopupFlag(url) {
|
|
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);
|
|
248
|
+
}
|
|
249
|
+
|
|
219
250
|
// src/utils/placement.ts
|
|
220
251
|
var IFRAME_WIDTH = 400;
|
|
221
252
|
var IFRAME_HEIGHT = 645;
|
|
@@ -826,6 +857,12 @@ var ConnectSDK = (() => {
|
|
|
826
857
|
this.loginPopup = context.loginPopup;
|
|
827
858
|
this.mode = context.mode;
|
|
828
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;
|
|
829
866
|
}
|
|
830
867
|
static setMode(mode) {
|
|
831
868
|
this.mode = mode;
|
|
@@ -846,6 +883,8 @@ var ConnectSDK = (() => {
|
|
|
846
883
|
this.loginPopup = null;
|
|
847
884
|
this.mode = 1 /* CONNECT */;
|
|
848
885
|
this.connectOrigin = "";
|
|
886
|
+
this.isIframeReady = false;
|
|
887
|
+
this.pendingPopupAuth = [];
|
|
849
888
|
}
|
|
850
889
|
static handleErrorMessage(event) {
|
|
851
890
|
switch (event.type) {
|
|
@@ -868,12 +907,25 @@ var ConnectSDK = (() => {
|
|
|
868
907
|
static relayToIframe(event) {
|
|
869
908
|
var _a;
|
|
870
909
|
const iframeWindow = (_a = this.iframe) == null ? void 0 : _a.contentWindow;
|
|
871
|
-
if (!iframeWindow) {
|
|
872
|
-
|
|
910
|
+
if (!this.isIframeReady || !iframeWindow) {
|
|
911
|
+
this.pendingPopupAuth.push(event);
|
|
873
912
|
return;
|
|
874
913
|
}
|
|
875
914
|
iframeWindow.postMessage(event.data, this.connectOrigin);
|
|
876
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
|
+
}
|
|
877
929
|
static relayToPopup(payload) {
|
|
878
930
|
if (!this.loginPopup) {
|
|
879
931
|
console.warn("[Benji Connect SDK] Skipping login auth relay, no login popup");
|
|
@@ -980,6 +1032,8 @@ var ConnectSDK = (() => {
|
|
|
980
1032
|
_MessageRouter.loginPopup = null;
|
|
981
1033
|
_MessageRouter.mode = 1 /* CONNECT */;
|
|
982
1034
|
_MessageRouter.connectOrigin = "";
|
|
1035
|
+
_MessageRouter.isIframeReady = false;
|
|
1036
|
+
_MessageRouter.pendingPopupAuth = [];
|
|
983
1037
|
_MessageRouter.errorEventHandler = (event) => {
|
|
984
1038
|
return _MessageRouter.handleErrorMessage(event);
|
|
985
1039
|
};
|
|
@@ -1009,6 +1063,7 @@ var ConnectSDK = (() => {
|
|
|
1009
1063
|
};
|
|
1010
1064
|
this.repositionListeners = null;
|
|
1011
1065
|
this.loginPopup = null;
|
|
1066
|
+
this.hasLoginPopupUrl = false;
|
|
1012
1067
|
this.popupClosedPollId = null;
|
|
1013
1068
|
this.isClosing = false;
|
|
1014
1069
|
/**
|
|
@@ -1018,6 +1073,10 @@ var ConnectSDK = (() => {
|
|
|
1018
1073
|
* while the in-flight open() still registers listeners and mounts the iframe.
|
|
1019
1074
|
*/
|
|
1020
1075
|
this.openAbortController = null;
|
|
1076
|
+
this.onIframeLoad = () => {
|
|
1077
|
+
if (this.isClosing || !this.iframe) return;
|
|
1078
|
+
MessageRouter.markIframeReady(this.iframe);
|
|
1079
|
+
};
|
|
1021
1080
|
this.sdkConfig = config;
|
|
1022
1081
|
if (!this.sdkConfig.environment) throw new Error("Environment is required");
|
|
1023
1082
|
if (!this.sdkConfig.token) throw new Error("Connect token is required");
|
|
@@ -1025,10 +1084,15 @@ var ConnectSDK = (() => {
|
|
|
1025
1084
|
this.sdkConfig.environment = environment;
|
|
1026
1085
|
configureConfig(environment);
|
|
1027
1086
|
}
|
|
1087
|
+
resolveAuthType() {
|
|
1088
|
+
var _a, _b;
|
|
1089
|
+
return (_b = (_a = this.sdkConfig.tokenData) == null ? void 0 : _a.authType) != null ? _b : this.sdkConfig.authType;
|
|
1090
|
+
}
|
|
1028
1091
|
isLoginPopupOnly() {
|
|
1029
1092
|
var _a;
|
|
1030
1093
|
const mode = (_a = this.sdkConfig.mode) != null ? _a : 1 /* CONNECT */;
|
|
1031
|
-
|
|
1094
|
+
const authType = this.resolveAuthType();
|
|
1095
|
+
return mode === 5 /* LOGIN */ && authType != null && authType !== 0 /* DUMMY */;
|
|
1032
1096
|
}
|
|
1033
1097
|
isSessionActive() {
|
|
1034
1098
|
return this.iframe != null || this.loginPopup != null;
|
|
@@ -1051,28 +1115,36 @@ var ConnectSDK = (() => {
|
|
|
1051
1115
|
});
|
|
1052
1116
|
}
|
|
1053
1117
|
openLoginPopup() {
|
|
1054
|
-
var _a, _b;
|
|
1118
|
+
var _a, _b, _c, _d;
|
|
1055
1119
|
const position = computePopupScreenPosition(
|
|
1056
1120
|
this.sdkConfig,
|
|
1057
1121
|
this.popupLayoutState,
|
|
1058
1122
|
{ warnOnInvalidAnchor: true }
|
|
1059
1123
|
);
|
|
1124
|
+
const partnerAuthLink = (_a = this.sdkConfig.tokenData) == null ? void 0 : _a.partnerAuthLink;
|
|
1125
|
+
const configToken = (_b = this.sdkConfig.tokenData) == null ? void 0 : _b.configToken;
|
|
1126
|
+
const preparedLink = partnerAuthLink && configToken ? preparePartnerAuthUrl(partnerAuthLink, configToken) : partnerAuthLink;
|
|
1127
|
+
const popupUrl = preparedLink ? appendLoginPopupFlag(preparedLink) : "about:blank";
|
|
1060
1128
|
this.loginPopup = window.open(
|
|
1061
|
-
|
|
1129
|
+
popupUrl,
|
|
1062
1130
|
"benji_partner_auth",
|
|
1063
1131
|
buildLoginPopupFeatures(position)
|
|
1064
1132
|
);
|
|
1065
1133
|
if (!this.loginPopup) {
|
|
1066
1134
|
console.warn("[Connect SDK] Failed to pre-open LOGIN partner auth popup");
|
|
1067
|
-
(
|
|
1068
|
-
|
|
1135
|
+
(_d = (_c = this.sdkConfig).onError) == null ? void 0 : _d.call(
|
|
1136
|
+
_c,
|
|
1069
1137
|
new Error("Popup blocked. Allow popups for this site and try again."),
|
|
1070
1138
|
"400",
|
|
1071
1139
|
{ context: buildContext() }
|
|
1072
1140
|
);
|
|
1073
1141
|
return false;
|
|
1074
1142
|
}
|
|
1075
|
-
|
|
1143
|
+
if (preparedLink) {
|
|
1144
|
+
this.hasLoginPopupUrl = true;
|
|
1145
|
+
} else {
|
|
1146
|
+
writeLoginPopupLoadingDocument(this.loginPopup);
|
|
1147
|
+
}
|
|
1076
1148
|
this.startPopupClosedPoll();
|
|
1077
1149
|
return true;
|
|
1078
1150
|
}
|
|
@@ -1145,8 +1217,9 @@ var ConnectSDK = (() => {
|
|
|
1145
1217
|
});
|
|
1146
1218
|
}
|
|
1147
1219
|
openUI() {
|
|
1148
|
-
var _a;
|
|
1220
|
+
var _a, _b;
|
|
1149
1221
|
if (this.iframe) return;
|
|
1222
|
+
MessageRouter.resetIframeReady();
|
|
1150
1223
|
const headless = this.isLoginPopupOnly();
|
|
1151
1224
|
this.container = document.createElement("div");
|
|
1152
1225
|
if (headless) {
|
|
@@ -1164,6 +1237,8 @@ var ConnectSDK = (() => {
|
|
|
1164
1237
|
url.searchParams.set("mode", String(this.sdkConfig.mode));
|
|
1165
1238
|
}
|
|
1166
1239
|
this.iframe = document.createElement("iframe");
|
|
1240
|
+
this.syncLoginContext((_a = this.sdkConfig.mode) != null ? _a : 1 /* CONNECT */);
|
|
1241
|
+
this.iframe.addEventListener("load", this.onIframeLoad);
|
|
1167
1242
|
this.iframe.src = url.toString();
|
|
1168
1243
|
this.container.appendChild(this.iframe);
|
|
1169
1244
|
document.body.appendChild(this.container);
|
|
@@ -1194,9 +1269,9 @@ var ConnectSDK = (() => {
|
|
|
1194
1269
|
}
|
|
1195
1270
|
);
|
|
1196
1271
|
this.container.addEventListener("click", (e) => {
|
|
1197
|
-
var _a2,
|
|
1272
|
+
var _a2, _b2;
|
|
1198
1273
|
if (e.target === this.container) {
|
|
1199
|
-
(
|
|
1274
|
+
(_b2 = (_a2 = this.sdkConfig).onExit) == null ? void 0 : _b2.call(_a2, {
|
|
1200
1275
|
context: buildContext(),
|
|
1201
1276
|
trigger: "TAPPED_OUT_OF_BOUNDS" /* TAPPED_OUT_OF_BOUNDS */
|
|
1202
1277
|
});
|
|
@@ -1204,15 +1279,17 @@ var ConnectSDK = (() => {
|
|
|
1204
1279
|
}
|
|
1205
1280
|
});
|
|
1206
1281
|
}
|
|
1207
|
-
this.syncLoginContext((
|
|
1282
|
+
this.syncLoginContext((_b = this.sdkConfig.mode) != null ? _b : 1 /* CONNECT */);
|
|
1208
1283
|
}
|
|
1209
1284
|
navigateLoginPopup(url) {
|
|
1285
|
+
if (this.hasLoginPopupUrl) return;
|
|
1210
1286
|
if (!this.loginPopup || this.loginPopup.closed) {
|
|
1211
1287
|
console.warn("[Connect SDK] Cannot navigate LOGIN popup \u2014 window unavailable");
|
|
1212
1288
|
return;
|
|
1213
1289
|
}
|
|
1214
1290
|
try {
|
|
1215
1291
|
this.loginPopup.location.href = url;
|
|
1292
|
+
this.hasLoginPopupUrl = true;
|
|
1216
1293
|
} catch (error) {
|
|
1217
1294
|
console.warn("[Connect SDK] Failed to navigate LOGIN popup", error);
|
|
1218
1295
|
}
|
|
@@ -1227,6 +1304,7 @@ var ConnectSDK = (() => {
|
|
|
1227
1304
|
console.warn("[Connect SDK] Error closing LOGIN popup", error);
|
|
1228
1305
|
} finally {
|
|
1229
1306
|
this.loginPopup = null;
|
|
1307
|
+
this.hasLoginPopupUrl = false;
|
|
1230
1308
|
}
|
|
1231
1309
|
}
|
|
1232
1310
|
close() {
|
|
@@ -1244,6 +1322,7 @@ var ConnectSDK = (() => {
|
|
|
1244
1322
|
this.repositionListeners = null;
|
|
1245
1323
|
this.layoutState.lastValidAnchoredLayout = null;
|
|
1246
1324
|
this.popupLayoutState.lastValidAnchoredLayout = null;
|
|
1325
|
+
this.iframe.removeEventListener("load", this.onIframeLoad);
|
|
1247
1326
|
if ((_b = this.container) == null ? void 0 : _b.parentNode) {
|
|
1248
1327
|
this.container.parentNode.removeChild(this.container);
|
|
1249
1328
|
}
|