@apps-in-toss/web-bridge 2.9.2 → 2.10.0
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/dist/index.cjs +348 -48
- package/dist/index.d.cts +38 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +348 -48
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -1035,6 +1035,197 @@ function resetAttachedBannerRegistry() {
|
|
|
1035
1035
|
attachedBannerRegistry = /* @__PURE__ */ new WeakMap();
|
|
1036
1036
|
}
|
|
1037
1037
|
|
|
1038
|
+
// src/toss-ad/constants.ts
|
|
1039
|
+
var ERROR_CODES = {
|
|
1040
|
+
NETWORK_ERROR: 1001,
|
|
1041
|
+
INVALID_REQUEST: 1002,
|
|
1042
|
+
NO_AD: 1003,
|
|
1043
|
+
ERROR: 1004,
|
|
1044
|
+
INVALID_SPACE: 1005,
|
|
1045
|
+
SDK_NOT_INITIALIZED: 1007,
|
|
1046
|
+
HTTP_TIMEOUT: 1008,
|
|
1047
|
+
EXECUTION_FAIL: 1009,
|
|
1048
|
+
INTERRUPTED: 1010,
|
|
1049
|
+
FAIL: 1011,
|
|
1050
|
+
BLOCKED: 1012,
|
|
1051
|
+
TIMEOUT: 1013,
|
|
1052
|
+
LIMITED_AD: 1014,
|
|
1053
|
+
CONSENT_REQUIRED: 1015,
|
|
1054
|
+
TEST_MODE: 1016,
|
|
1055
|
+
INTERNAL_ERROR: 1017
|
|
1056
|
+
};
|
|
1057
|
+
var NON_ERROR_STATUSES = /* @__PURE__ */ new Set(["OK", "TEST_MODE", "NO_AD"]);
|
|
1058
|
+
var API_RESULT_ERROR_CODE_MAP = {
|
|
1059
|
+
HTTP_TIMEOUT: ERROR_CODES.HTTP_TIMEOUT,
|
|
1060
|
+
NETWORK_ERROR: ERROR_CODES.NETWORK_ERROR,
|
|
1061
|
+
EXECUTION_FAIL: ERROR_CODES.EXECUTION_FAIL,
|
|
1062
|
+
INTERRUPTED: ERROR_CODES.INTERRUPTED,
|
|
1063
|
+
INTERNAL_ERROR: ERROR_CODES.ERROR,
|
|
1064
|
+
FAIL: ERROR_CODES.FAIL
|
|
1065
|
+
};
|
|
1066
|
+
var AD_STATUS_ERROR_CODE_MAP = {
|
|
1067
|
+
BLOCKED: ERROR_CODES.BLOCKED,
|
|
1068
|
+
ERROR: ERROR_CODES.ERROR,
|
|
1069
|
+
TIMEOUT: ERROR_CODES.TIMEOUT,
|
|
1070
|
+
INVALID_SPACE: ERROR_CODES.INVALID_SPACE,
|
|
1071
|
+
INVALID_REQUEST: ERROR_CODES.INVALID_REQUEST,
|
|
1072
|
+
LIMITED_AD: ERROR_CODES.LIMITED_AD,
|
|
1073
|
+
CONSENT_REQUIRED: ERROR_CODES.CONSENT_REQUIRED
|
|
1074
|
+
};
|
|
1075
|
+
|
|
1076
|
+
// src/toss-ad/tossAdEventLog.ts
|
|
1077
|
+
var tossAdEventLog = createAsyncBridge("tossAdEventLog");
|
|
1078
|
+
|
|
1079
|
+
// src/toss-ad/errorTracking.ts
|
|
1080
|
+
var BANNER_ERROR_LOG_NAME = "display_ads_all::error__banner";
|
|
1081
|
+
function logBannerErrorToAppLog(info) {
|
|
1082
|
+
void tossAdEventLog({
|
|
1083
|
+
log_name: BANNER_ERROR_LOG_NAME,
|
|
1084
|
+
log_type: "debug",
|
|
1085
|
+
params: {
|
|
1086
|
+
error_code: info.errorCode,
|
|
1087
|
+
error_message: info.errorMessage,
|
|
1088
|
+
ad_group_id: info.adGroupId,
|
|
1089
|
+
mediation_id: info.mediationId,
|
|
1090
|
+
event_context_token: info.eventContextToken
|
|
1091
|
+
}
|
|
1092
|
+
}).catch(() => {
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
function trackBannerError(info) {
|
|
1096
|
+
if (info.endpointError) {
|
|
1097
|
+
void fetch(info.endpointError, {
|
|
1098
|
+
method: "POST",
|
|
1099
|
+
headers: { "Content-Type": "application/json" },
|
|
1100
|
+
body: JSON.stringify({
|
|
1101
|
+
eventContextToken: info.eventContextToken,
|
|
1102
|
+
mediationId: info.mediationId,
|
|
1103
|
+
errorCode: info.errorCode,
|
|
1104
|
+
errorMessage: info.errorMessage
|
|
1105
|
+
})
|
|
1106
|
+
// POST 실패(CORS·네트워크) → 앱로그 fallback (endpoint와 같은 채널이라 함께 막히는 경우 대비).
|
|
1107
|
+
}).catch(() => logBannerErrorToAppLog(info));
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
logBannerErrorToAppLog(info);
|
|
1111
|
+
}
|
|
1112
|
+
function trackResponseError(response, adGroupId, context) {
|
|
1113
|
+
if (response.resultType !== "SUCCESS") {
|
|
1114
|
+
trackBannerError({
|
|
1115
|
+
adGroupId,
|
|
1116
|
+
errorCode: API_RESULT_ERROR_CODE_MAP[response.resultType] ?? ERROR_CODES.INTERNAL_ERROR,
|
|
1117
|
+
errorMessage: response.error?.reason ?? response.resultType
|
|
1118
|
+
});
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
const success = response.success;
|
|
1122
|
+
if (!success || NON_ERROR_STATUSES.has(String(success.status))) {
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
trackBannerError({
|
|
1126
|
+
adGroupId,
|
|
1127
|
+
errorCode: AD_STATUS_ERROR_CODE_MAP[String(success.status)] ?? ERROR_CODES.INTERNAL_ERROR,
|
|
1128
|
+
mediationId: context.mediationId,
|
|
1129
|
+
eventContextToken: context.eventContextToken,
|
|
1130
|
+
errorMessage: `Ad response status: ${success.status}`,
|
|
1131
|
+
endpointError: context.errorEndpoint
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// src/toss-ad/eventTracking.ts
|
|
1136
|
+
async function handleTrackingRequest(request) {
|
|
1137
|
+
if (request.type === "EVENT_TRACKING") {
|
|
1138
|
+
await sendEventTracking(request);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
async function sendEventTracking(request) {
|
|
1142
|
+
if (request.urls.length === 0) {
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
const body = JSON.stringify({ type: request.eventName, data: request.eventPayload });
|
|
1146
|
+
const headers = { "Content-Type": "application/json" };
|
|
1147
|
+
if (request.requestId) {
|
|
1148
|
+
headers["X-Toss-RequestId"] = request.requestId;
|
|
1149
|
+
}
|
|
1150
|
+
const settled = await Promise.allSettled(
|
|
1151
|
+
request.urls.map(
|
|
1152
|
+
(url) => fetch(url, { method: "POST", headers, body }).then((response) => {
|
|
1153
|
+
if (!response.ok) {
|
|
1154
|
+
throw new Error(`HTTP ${response.status}`);
|
|
1155
|
+
}
|
|
1156
|
+
})
|
|
1157
|
+
)
|
|
1158
|
+
);
|
|
1159
|
+
const failedCount = settled.filter((result) => result.status === "rejected").length;
|
|
1160
|
+
if (failedCount === 0) {
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
trackBannerError({
|
|
1164
|
+
adGroupId: request.adGroupId,
|
|
1165
|
+
errorCode: ERROR_CODES.NETWORK_ERROR,
|
|
1166
|
+
errorMessage: `EVENT_TRACKING failed: ${request.eventName} (${failedCount}/${request.urls.length})`,
|
|
1167
|
+
mediationId: request.mediationId || void 0,
|
|
1168
|
+
eventContextToken: request.eventContextToken || void 0,
|
|
1169
|
+
endpointError: request.errorEndpoint
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// src/toss-ad/fetchAppsInTossAdSchema.ts
|
|
1174
|
+
var APPS_IN_TOSS_AD_SPEC_VERSION = "1.4.1";
|
|
1175
|
+
var FETCH_APPS_IN_TOSS_AD_DEVICE_MACRO = "{{device}}";
|
|
1176
|
+
var TOSS_APP_BUNDLE_IDS = {
|
|
1177
|
+
ios: "com.vivarepublica.cash",
|
|
1178
|
+
android: "viva.republica.toss"
|
|
1179
|
+
};
|
|
1180
|
+
function resolveTossAppBundleId(os) {
|
|
1181
|
+
return TOSS_APP_BUNDLE_IDS[os];
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// src/toss-ad/normalize.ts
|
|
1185
|
+
var LIST_BANNER_STYLE_ID = "1";
|
|
1186
|
+
var NATIVE_IMAGE_STYLE_ID = "2";
|
|
1187
|
+
var AVAILABLE_STYLE_IDS = [LIST_BANNER_STYLE_ID, NATIVE_IMAGE_STYLE_ID];
|
|
1188
|
+
var SUPPORTED_STYLE_IDS = new Set(AVAILABLE_STYLE_IDS);
|
|
1189
|
+
function normalizeAdResponse(adResponse) {
|
|
1190
|
+
const ads = Array.isArray(adResponse.ads) ? adResponse.ads.filter((ad) => SUPPORTED_STYLE_IDS.has(String(ad.styleId))) : [];
|
|
1191
|
+
return {
|
|
1192
|
+
requestId: adResponse.requestId ?? "",
|
|
1193
|
+
// SDK가 event 트래킹 위임(onTrackingRequest) 시 응답 top-level에서 읽어 실어 보낸다.
|
|
1194
|
+
// 떼어내면 SDK가 토큰을 못 보고, event 트래킹 실패 시 endpoint.error에서 토큰이 빠진다.
|
|
1195
|
+
eventContextToken: adResponse.eventContextToken,
|
|
1196
|
+
status: adResponse.status ?? "OK",
|
|
1197
|
+
ads,
|
|
1198
|
+
ext: adResponse.ext
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
function normalizeApiResponse(raw) {
|
|
1202
|
+
if (isApiResponse(raw)) {
|
|
1203
|
+
if (raw.resultType !== "SUCCESS") {
|
|
1204
|
+
return raw;
|
|
1205
|
+
}
|
|
1206
|
+
if (!raw.success) {
|
|
1207
|
+
return {
|
|
1208
|
+
resultType: "FAIL",
|
|
1209
|
+
error: { reason: "fetchTossAd returned SUCCESS without payload" }
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
return { ...raw, success: normalizeAdResponse(raw.success) };
|
|
1213
|
+
}
|
|
1214
|
+
if (isAdResponse(raw)) {
|
|
1215
|
+
return {
|
|
1216
|
+
resultType: "SUCCESS",
|
|
1217
|
+
success: normalizeAdResponse(raw)
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
return { resultType: "FAIL", error: { reason: "Invalid response from fetchTossAd" } };
|
|
1221
|
+
}
|
|
1222
|
+
function isApiResponse(payload) {
|
|
1223
|
+
return Boolean(payload && typeof payload === "object" && "resultType" in payload);
|
|
1224
|
+
}
|
|
1225
|
+
function isAdResponse(payload) {
|
|
1226
|
+
return Boolean(payload && typeof payload === "object" && "ads" in payload);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1038
1229
|
// src/toss-ad/opener.ts
|
|
1039
1230
|
var openURL = createAsyncBridge("openURL");
|
|
1040
1231
|
function openUrlOpener(url) {
|
|
@@ -1049,8 +1240,49 @@ function supertossWeb(uri) {
|
|
|
1049
1240
|
return `supertoss://web?url=${encodeURIComponent(uri)}&external=true`;
|
|
1050
1241
|
}
|
|
1051
1242
|
|
|
1243
|
+
// src/toss-ad/resultTracking.ts
|
|
1244
|
+
async function fireResultTracking(response, adGroupId, ctx) {
|
|
1245
|
+
const status = response.success?.status;
|
|
1246
|
+
if (response.resultType !== "SUCCESS" || status !== "OK" && status !== "TEST_MODE") {
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
if (ctx.resultEndpoint == null || ctx.mediationId == null) {
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
const body = {
|
|
1253
|
+
mediationId: ctx.mediationId,
|
|
1254
|
+
spaceUnitId: adGroupId,
|
|
1255
|
+
requestId: response.success?.requestId ?? "",
|
|
1256
|
+
eventContextToken: ctx.eventContextToken,
|
|
1257
|
+
winnerSource: "TOSS",
|
|
1258
|
+
content: null,
|
|
1259
|
+
adUnitId: ctx.adUnitId ?? null,
|
|
1260
|
+
placementId: ctx.placementId ?? null,
|
|
1261
|
+
tossFailed: null
|
|
1262
|
+
};
|
|
1263
|
+
try {
|
|
1264
|
+
const res = await fetch(ctx.resultEndpoint, {
|
|
1265
|
+
method: "POST",
|
|
1266
|
+
headers: { "Content-Type": "application/json" },
|
|
1267
|
+
body: JSON.stringify(body)
|
|
1268
|
+
});
|
|
1269
|
+
if (!res.ok) {
|
|
1270
|
+
throw new Error(`HTTP ${res.status}`);
|
|
1271
|
+
}
|
|
1272
|
+
} catch (error) {
|
|
1273
|
+
trackBannerError({
|
|
1274
|
+
adGroupId,
|
|
1275
|
+
errorCode: ERROR_CODES.NETWORK_ERROR,
|
|
1276
|
+
errorMessage: `RESULT_TRACKING failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1277
|
+
mediationId: ctx.mediationId,
|
|
1278
|
+
eventContextToken: ctx.eventContextToken,
|
|
1279
|
+
endpointError: ctx.errorEndpoint
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1052
1284
|
// src/toss-ad/scriptLoader.ts
|
|
1053
|
-
var DEFAULT_SDK_URL = "https://static.toss.im/ads/sdk/toss-ads-space-kit-1.
|
|
1285
|
+
var DEFAULT_SDK_URL = "https://static.toss.im/ads/sdk/toss-ads-space-kit-3.1.0.js";
|
|
1054
1286
|
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
1055
1287
|
var pendingLoad = null;
|
|
1056
1288
|
function getAdsSdk() {
|
|
@@ -1106,12 +1338,79 @@ function loadAdsSdk() {
|
|
|
1106
1338
|
return promise;
|
|
1107
1339
|
}
|
|
1108
1340
|
|
|
1341
|
+
// package.json
|
|
1342
|
+
var package_default = {
|
|
1343
|
+
name: "@apps-in-toss/web-bridge",
|
|
1344
|
+
type: "module",
|
|
1345
|
+
version: "2.10.0",
|
|
1346
|
+
description: "Web Bridge for Apps In Toss",
|
|
1347
|
+
scripts: {
|
|
1348
|
+
typecheck: "tsc --noEmit",
|
|
1349
|
+
lint: "eslint .",
|
|
1350
|
+
build: "tsup"
|
|
1351
|
+
},
|
|
1352
|
+
main: "./dist/index.cjs",
|
|
1353
|
+
module: "./dist/index.js",
|
|
1354
|
+
types: "./dist/index.d.ts",
|
|
1355
|
+
exports: {
|
|
1356
|
+
".": {
|
|
1357
|
+
import: {
|
|
1358
|
+
types: "./dist/index.d.ts",
|
|
1359
|
+
default: "./dist/index.js"
|
|
1360
|
+
},
|
|
1361
|
+
require: {
|
|
1362
|
+
types: "./dist/index.d.cts",
|
|
1363
|
+
default: "./dist/index.cjs"
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
},
|
|
1367
|
+
files: [
|
|
1368
|
+
"dist"
|
|
1369
|
+
],
|
|
1370
|
+
dependencies: {
|
|
1371
|
+
"@apps-in-toss/types": "workspace:*"
|
|
1372
|
+
},
|
|
1373
|
+
devDependencies: {
|
|
1374
|
+
"@apps-in-toss/bridge-core": "workspace:*",
|
|
1375
|
+
"@apps-in-toss/native-modules": "workspace:^",
|
|
1376
|
+
"@swc/core": "^1.12.7",
|
|
1377
|
+
picocolors: "^1.1.1",
|
|
1378
|
+
"ts-morph": "^26.0.0",
|
|
1379
|
+
tsup: "^8.5.0",
|
|
1380
|
+
typescript: "5.8.3",
|
|
1381
|
+
vitest: "^3.1.2"
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
|
|
1385
|
+
// src/toss-ad/sdkVersion.ts
|
|
1386
|
+
var SDK_VERSION = package_default.version;
|
|
1387
|
+
|
|
1388
|
+
// src/toss-ad/trackingContext.ts
|
|
1389
|
+
function nonEmptyString(value) {
|
|
1390
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1391
|
+
}
|
|
1392
|
+
function extractAdTrackingContext(response) {
|
|
1393
|
+
const mediation = response?.ext?.mediation;
|
|
1394
|
+
return {
|
|
1395
|
+
mediationId: nonEmptyString(mediation?.mediationId),
|
|
1396
|
+
eventContextToken: nonEmptyString(response?.eventContextToken),
|
|
1397
|
+
resultEndpoint: nonEmptyString(mediation?.endpoint?.result),
|
|
1398
|
+
errorEndpoint: nonEmptyString(mediation?.endpoint?.error),
|
|
1399
|
+
adUnitId: nonEmptyString(mediation?.admob?.adUnitId),
|
|
1400
|
+
placementId: typeof mediation?.admob?.placementId === "number" ? mediation.admob.placementId : void 0
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1109
1404
|
// src/toss-ad/index.ts
|
|
1110
1405
|
var fetchTossAd = Object.assign(createEventBridge("fetchTossAd"), {
|
|
1111
1406
|
isSupported: createConstantBridge("fetchTossAd_isSupported")
|
|
1112
1407
|
});
|
|
1113
|
-
var
|
|
1114
|
-
|
|
1408
|
+
var fetchAppsInTossAd = Object.assign(createEventBridge("fetchAppsInTossAd"), {
|
|
1409
|
+
isSupported: createConstantBridge("fetchAppsInTossAd_isSupported")
|
|
1410
|
+
});
|
|
1411
|
+
var getPlatformOS = createConstantBridge("getPlatformOS");
|
|
1412
|
+
var getTossAppVersion = createConstantBridge("getTossAppVersion");
|
|
1413
|
+
var WEB_SDK_ID = "108";
|
|
1115
1414
|
var INVALID_AD_GROUP_ID_ERROR_MESSAGE = "\uC798\uBABB\uB41C \uC694\uCCAD\uC774\uC5D0\uC694. \uD544\uC694\uD55C \uAC12\uC774 \uBE44\uC5B4 \uC788\uC5B4\uC694.";
|
|
1116
1415
|
function normalizeAdGroupId(adGroupId) {
|
|
1117
1416
|
return adGroupId.trim();
|
|
@@ -1148,62 +1447,56 @@ function fetchTossAdPromise(options) {
|
|
|
1148
1447
|
});
|
|
1149
1448
|
});
|
|
1150
1449
|
}
|
|
1151
|
-
function
|
|
1152
|
-
|
|
1153
|
-
return {
|
|
1154
|
-
requestId: adResponse.requestId ?? "",
|
|
1155
|
-
status: adResponse.status ?? "OK",
|
|
1156
|
-
ads,
|
|
1157
|
-
ext: adResponse.ext
|
|
1158
|
-
};
|
|
1450
|
+
function getSubBundle() {
|
|
1451
|
+
return globalThis.__granite?.app?.name ?? "";
|
|
1159
1452
|
}
|
|
1160
|
-
function
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1453
|
+
function fetchAppsInTossAdPromise(spaceUnitId) {
|
|
1454
|
+
const platformOS = getPlatformOS();
|
|
1455
|
+
const tossAppVersion = getTossAppVersion();
|
|
1456
|
+
const body = {
|
|
1457
|
+
specVersion: APPS_IN_TOSS_AD_SPEC_VERSION,
|
|
1458
|
+
platform: "WEB",
|
|
1459
|
+
spaceUnitId,
|
|
1460
|
+
sdkVersion: SDK_VERSION,
|
|
1461
|
+
availableStyleIds: AVAILABLE_STYLE_IDS,
|
|
1462
|
+
// device는 매크로키만 보내고 native가 실제 device로 치환한다.
|
|
1463
|
+
device: FETCH_APPS_IN_TOSS_AD_DEVICE_MACRO,
|
|
1464
|
+
app: {
|
|
1465
|
+
version: tossAppVersion,
|
|
1466
|
+
bundle: resolveTossAppBundleId(platformOS),
|
|
1467
|
+
subBundle: getSubBundle()
|
|
1170
1468
|
}
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
resultType: "SUCCESS",
|
|
1176
|
-
success: normalizeAdResponse(raw)
|
|
1177
|
-
};
|
|
1178
|
-
}
|
|
1179
|
-
return { resultType: "FAIL", error: { reason: "Invalid response from fetchTossAd" } };
|
|
1180
|
-
}
|
|
1181
|
-
function isApiResponse(payload) {
|
|
1182
|
-
return Boolean(payload && typeof payload === "object" && "resultType" in payload);
|
|
1183
|
-
}
|
|
1184
|
-
function isAdResponse(payload) {
|
|
1185
|
-
return Boolean(payload && typeof payload === "object" && "ads" in payload);
|
|
1469
|
+
};
|
|
1470
|
+
return new Promise((resolve, reject) => {
|
|
1471
|
+
fetchAppsInTossAd({ options: body, onEvent: resolve, onError: reject });
|
|
1472
|
+
});
|
|
1186
1473
|
}
|
|
1187
1474
|
function createCustomAdFetcher() {
|
|
1188
1475
|
return async (_endpoint, request) => {
|
|
1189
1476
|
const spaceUnitId = normalizeAdGroupId(request.spaceUnitId);
|
|
1190
1477
|
if (spaceUnitId.length === 0) {
|
|
1478
|
+
trackBannerError({
|
|
1479
|
+
adGroupId: spaceUnitId,
|
|
1480
|
+
errorCode: ERROR_CODES.INVALID_SPACE,
|
|
1481
|
+
errorMessage: INVALID_AD_GROUP_ID_ERROR_MESSAGE
|
|
1482
|
+
});
|
|
1191
1483
|
return createInvalidAdGroupIdResponse();
|
|
1192
1484
|
}
|
|
1193
1485
|
try {
|
|
1194
|
-
const raw = await fetchTossAdPromise({
|
|
1486
|
+
const raw = fetchAppsInTossAd.isSupported() ? await fetchAppsInTossAdPromise(spaceUnitId) : await fetchTossAdPromise({
|
|
1195
1487
|
adGroupId: spaceUnitId,
|
|
1196
|
-
sdkId:
|
|
1197
|
-
availableStyleIds:
|
|
1488
|
+
sdkId: WEB_SDK_ID,
|
|
1489
|
+
availableStyleIds: AVAILABLE_STYLE_IDS
|
|
1198
1490
|
});
|
|
1199
|
-
|
|
1491
|
+
const response = normalizeApiResponse(raw);
|
|
1492
|
+
const trackingContext = extractAdTrackingContext(response.success);
|
|
1493
|
+
trackResponseError(response, spaceUnitId, trackingContext);
|
|
1494
|
+
void fireResultTracking(response, spaceUnitId, trackingContext);
|
|
1495
|
+
return response;
|
|
1200
1496
|
} catch (error) {
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
reason: error instanceof Error ? error.message : "Unknown fetchTossAd error"
|
|
1205
|
-
}
|
|
1206
|
-
};
|
|
1497
|
+
const reason = error instanceof Error ? error.message : "Unknown fetchTossAd error";
|
|
1498
|
+
trackBannerError({ adGroupId: spaceUnitId, errorCode: ERROR_CODES.NETWORK_ERROR, errorMessage: reason });
|
|
1499
|
+
return { resultType: "FAIL", error: { reason } };
|
|
1207
1500
|
}
|
|
1208
1501
|
};
|
|
1209
1502
|
}
|
|
@@ -1229,7 +1522,12 @@ function initialize(options) {
|
|
|
1229
1522
|
return;
|
|
1230
1523
|
}
|
|
1231
1524
|
const customAdFetcher = createCustomAdFetcher();
|
|
1232
|
-
const config = {
|
|
1525
|
+
const config = {
|
|
1526
|
+
environment: "live",
|
|
1527
|
+
customAdFetcher,
|
|
1528
|
+
opener: openUrlOpener,
|
|
1529
|
+
onTrackingRequest: handleTrackingRequest
|
|
1530
|
+
};
|
|
1233
1531
|
sdk.init(config);
|
|
1234
1532
|
});
|
|
1235
1533
|
pendingLoad2 = initPromise;
|
|
@@ -1406,7 +1704,7 @@ function attachBanner(adGroupId, target, options = {}) {
|
|
|
1406
1704
|
autoLoad: true,
|
|
1407
1705
|
theme: theme === "auto" ? void 0 : theme,
|
|
1408
1706
|
renderPadding: (styleId) => {
|
|
1409
|
-
if (styleId ===
|
|
1707
|
+
if (styleId === LIST_BANNER_STYLE_ID) {
|
|
1410
1708
|
return DEFAULT_ATTACH_PADDING_BANNER;
|
|
1411
1709
|
}
|
|
1412
1710
|
return DEFAULT_ATTACH_PADDING_NATIVE_IMAGE;
|
|
@@ -1479,6 +1777,8 @@ function wrapCallbacks(adGroupId, callbacks) {
|
|
|
1479
1777
|
});
|
|
1480
1778
|
callbacks.onAdImpression?.(mapEvent(payload));
|
|
1481
1779
|
},
|
|
1780
|
+
// 에러 트래킹은 응답을 소유한 createCustomAdFetcher가 담당한다. 콜백은 publisher 전달만.
|
|
1781
|
+
// 응답 수신 후 에러의 mediationId는 SDK(ads-sdk/web)가 error에 실어주므로 그대로 전달한다.
|
|
1482
1782
|
onAdFailedToRender: (payload) => callbacks.onAdFailedToRender?.({
|
|
1483
1783
|
...mapEvent(payload),
|
|
1484
1784
|
error: payload?.error ?? { code: 0, message: "UNKNOWN" }
|
package/dist/index.d.cts
CHANGED
|
@@ -885,6 +885,22 @@ interface RuntimeInfo {
|
|
|
885
885
|
safeAreaInsets?: SafeAreaInsets;
|
|
886
886
|
}
|
|
887
887
|
type AdOpener = (url: string, target?: string, features?: string) => void;
|
|
888
|
+
/** SDK가 `onTrackingRequest`로 위임하는 트래킹 요청. ads-sdk/web의 동형 계약. */
|
|
889
|
+
interface TrackingRequestBase {
|
|
890
|
+
adGroupId: string;
|
|
891
|
+
requestId?: string;
|
|
892
|
+
creativeId?: string;
|
|
893
|
+
mediationId?: string;
|
|
894
|
+
eventContextToken?: string;
|
|
895
|
+
errorEndpoint?: string;
|
|
896
|
+
}
|
|
897
|
+
interface EventTrackingRequest extends TrackingRequestBase {
|
|
898
|
+
type: 'EVENT_TRACKING';
|
|
899
|
+
eventName: string;
|
|
900
|
+
urls: string[];
|
|
901
|
+
eventPayload: string;
|
|
902
|
+
}
|
|
903
|
+
type TrackingRequest = EventTrackingRequest;
|
|
888
904
|
interface AdConfig {
|
|
889
905
|
environment: Environment;
|
|
890
906
|
apiEndpoint?: string;
|
|
@@ -893,6 +909,11 @@ interface AdConfig {
|
|
|
893
909
|
runtimeInfo?: RuntimeInfo;
|
|
894
910
|
opener?: AdOpener;
|
|
895
911
|
customAdFetcher?: CustomAdFetcher;
|
|
912
|
+
/**
|
|
913
|
+
* 트래킹 전송 위임(SDK 3.1.0+). 지정 시 SDK는 직접 전송하지 않고 이 콜백에 위임한다.
|
|
914
|
+
* fire-and-forget이며 실패 관측은 사용측 책임. 현재 위임 대상은 EVENT_TRACKING이다.
|
|
915
|
+
*/
|
|
916
|
+
onTrackingRequest?: (request: TrackingRequest) => void | Promise<void>;
|
|
896
917
|
}
|
|
897
918
|
interface AdRequest {
|
|
898
919
|
spaceUnitId: string;
|
|
@@ -907,6 +928,7 @@ interface AdRequest {
|
|
|
907
928
|
type AdResponseStatus = 'OK' | 'NO_AD' | 'BLOCKED' | 'ERROR' | 'TIMEOUT' | 'INVALID_SPACE' | 'INVALID_REQUEST' | 'LIMITED_AD' | 'CONSENT_REQUIRED' | 'TEST_MODE';
|
|
908
929
|
interface AdResponse {
|
|
909
930
|
requestId?: string;
|
|
931
|
+
eventContextToken?: string;
|
|
910
932
|
status?: AdResponseStatus;
|
|
911
933
|
ads: Ad[];
|
|
912
934
|
ext?: AdResponseExt;
|
|
@@ -919,6 +941,19 @@ interface Ad {
|
|
|
919
941
|
eventPayload?: string;
|
|
920
942
|
requestId?: string;
|
|
921
943
|
}
|
|
944
|
+
interface TossAdsMediation {
|
|
945
|
+
mediationId: string;
|
|
946
|
+
priority: Array<'TOSS' | 'ADMOB'>;
|
|
947
|
+
admob: {
|
|
948
|
+
adUnitId: string;
|
|
949
|
+
placementId: number;
|
|
950
|
+
} | null;
|
|
951
|
+
endpoint?: {
|
|
952
|
+
result?: string;
|
|
953
|
+
exposure?: string;
|
|
954
|
+
error?: string;
|
|
955
|
+
};
|
|
956
|
+
}
|
|
922
957
|
interface AdResponseExt {
|
|
923
958
|
skippableOffsetSeconds?: number;
|
|
924
959
|
reward?: {
|
|
@@ -927,6 +962,7 @@ interface AdResponseExt {
|
|
|
927
962
|
};
|
|
928
963
|
isAdBadgeEnabled?: boolean;
|
|
929
964
|
refetchSeconds?: number | null;
|
|
965
|
+
mediation?: TossAdsMediation;
|
|
930
966
|
}
|
|
931
967
|
interface ApiResponse<T> {
|
|
932
968
|
resultType: 'SUCCESS' | 'HTTP_TIMEOUT' | 'NETWORK_ERROR' | 'EXECUTION_FAIL' | 'INTERRUPTED' | 'INTERNAL_ERROR' | 'FAIL';
|
|
@@ -958,10 +994,12 @@ interface BannerSlotErrorPayload {
|
|
|
958
994
|
slotId: string;
|
|
959
995
|
adGroupId: string;
|
|
960
996
|
adMetadata: Record<string, never>;
|
|
997
|
+
/** `mediationId`는 `/api-web/v3/benefit-ads/display-ad/ads` 응답 수신 후 발생한 에러에만 포함된다. */
|
|
961
998
|
error: {
|
|
962
999
|
code: number;
|
|
963
1000
|
message: string;
|
|
964
1001
|
domain?: string;
|
|
1002
|
+
mediationId?: string;
|
|
965
1003
|
};
|
|
966
1004
|
}
|
|
967
1005
|
interface BannerSlotCallbacks {
|
package/dist/index.d.ts
CHANGED
|
@@ -885,6 +885,22 @@ interface RuntimeInfo {
|
|
|
885
885
|
safeAreaInsets?: SafeAreaInsets;
|
|
886
886
|
}
|
|
887
887
|
type AdOpener = (url: string, target?: string, features?: string) => void;
|
|
888
|
+
/** SDK가 `onTrackingRequest`로 위임하는 트래킹 요청. ads-sdk/web의 동형 계약. */
|
|
889
|
+
interface TrackingRequestBase {
|
|
890
|
+
adGroupId: string;
|
|
891
|
+
requestId?: string;
|
|
892
|
+
creativeId?: string;
|
|
893
|
+
mediationId?: string;
|
|
894
|
+
eventContextToken?: string;
|
|
895
|
+
errorEndpoint?: string;
|
|
896
|
+
}
|
|
897
|
+
interface EventTrackingRequest extends TrackingRequestBase {
|
|
898
|
+
type: 'EVENT_TRACKING';
|
|
899
|
+
eventName: string;
|
|
900
|
+
urls: string[];
|
|
901
|
+
eventPayload: string;
|
|
902
|
+
}
|
|
903
|
+
type TrackingRequest = EventTrackingRequest;
|
|
888
904
|
interface AdConfig {
|
|
889
905
|
environment: Environment;
|
|
890
906
|
apiEndpoint?: string;
|
|
@@ -893,6 +909,11 @@ interface AdConfig {
|
|
|
893
909
|
runtimeInfo?: RuntimeInfo;
|
|
894
910
|
opener?: AdOpener;
|
|
895
911
|
customAdFetcher?: CustomAdFetcher;
|
|
912
|
+
/**
|
|
913
|
+
* 트래킹 전송 위임(SDK 3.1.0+). 지정 시 SDK는 직접 전송하지 않고 이 콜백에 위임한다.
|
|
914
|
+
* fire-and-forget이며 실패 관측은 사용측 책임. 현재 위임 대상은 EVENT_TRACKING이다.
|
|
915
|
+
*/
|
|
916
|
+
onTrackingRequest?: (request: TrackingRequest) => void | Promise<void>;
|
|
896
917
|
}
|
|
897
918
|
interface AdRequest {
|
|
898
919
|
spaceUnitId: string;
|
|
@@ -907,6 +928,7 @@ interface AdRequest {
|
|
|
907
928
|
type AdResponseStatus = 'OK' | 'NO_AD' | 'BLOCKED' | 'ERROR' | 'TIMEOUT' | 'INVALID_SPACE' | 'INVALID_REQUEST' | 'LIMITED_AD' | 'CONSENT_REQUIRED' | 'TEST_MODE';
|
|
908
929
|
interface AdResponse {
|
|
909
930
|
requestId?: string;
|
|
931
|
+
eventContextToken?: string;
|
|
910
932
|
status?: AdResponseStatus;
|
|
911
933
|
ads: Ad[];
|
|
912
934
|
ext?: AdResponseExt;
|
|
@@ -919,6 +941,19 @@ interface Ad {
|
|
|
919
941
|
eventPayload?: string;
|
|
920
942
|
requestId?: string;
|
|
921
943
|
}
|
|
944
|
+
interface TossAdsMediation {
|
|
945
|
+
mediationId: string;
|
|
946
|
+
priority: Array<'TOSS' | 'ADMOB'>;
|
|
947
|
+
admob: {
|
|
948
|
+
adUnitId: string;
|
|
949
|
+
placementId: number;
|
|
950
|
+
} | null;
|
|
951
|
+
endpoint?: {
|
|
952
|
+
result?: string;
|
|
953
|
+
exposure?: string;
|
|
954
|
+
error?: string;
|
|
955
|
+
};
|
|
956
|
+
}
|
|
922
957
|
interface AdResponseExt {
|
|
923
958
|
skippableOffsetSeconds?: number;
|
|
924
959
|
reward?: {
|
|
@@ -927,6 +962,7 @@ interface AdResponseExt {
|
|
|
927
962
|
};
|
|
928
963
|
isAdBadgeEnabled?: boolean;
|
|
929
964
|
refetchSeconds?: number | null;
|
|
965
|
+
mediation?: TossAdsMediation;
|
|
930
966
|
}
|
|
931
967
|
interface ApiResponse<T> {
|
|
932
968
|
resultType: 'SUCCESS' | 'HTTP_TIMEOUT' | 'NETWORK_ERROR' | 'EXECUTION_FAIL' | 'INTERRUPTED' | 'INTERNAL_ERROR' | 'FAIL';
|
|
@@ -958,10 +994,12 @@ interface BannerSlotErrorPayload {
|
|
|
958
994
|
slotId: string;
|
|
959
995
|
adGroupId: string;
|
|
960
996
|
adMetadata: Record<string, never>;
|
|
997
|
+
/** `mediationId`는 `/api-web/v3/benefit-ads/display-ad/ads` 응답 수신 후 발생한 에러에만 포함된다. */
|
|
961
998
|
error: {
|
|
962
999
|
code: number;
|
|
963
1000
|
message: string;
|
|
964
1001
|
domain?: string;
|
|
1002
|
+
mediationId?: string;
|
|
965
1003
|
};
|
|
966
1004
|
}
|
|
967
1005
|
interface BannerSlotCallbacks {
|
package/dist/index.js
CHANGED
|
@@ -986,6 +986,197 @@ function resetAttachedBannerRegistry() {
|
|
|
986
986
|
attachedBannerRegistry = /* @__PURE__ */ new WeakMap();
|
|
987
987
|
}
|
|
988
988
|
|
|
989
|
+
// src/toss-ad/constants.ts
|
|
990
|
+
var ERROR_CODES = {
|
|
991
|
+
NETWORK_ERROR: 1001,
|
|
992
|
+
INVALID_REQUEST: 1002,
|
|
993
|
+
NO_AD: 1003,
|
|
994
|
+
ERROR: 1004,
|
|
995
|
+
INVALID_SPACE: 1005,
|
|
996
|
+
SDK_NOT_INITIALIZED: 1007,
|
|
997
|
+
HTTP_TIMEOUT: 1008,
|
|
998
|
+
EXECUTION_FAIL: 1009,
|
|
999
|
+
INTERRUPTED: 1010,
|
|
1000
|
+
FAIL: 1011,
|
|
1001
|
+
BLOCKED: 1012,
|
|
1002
|
+
TIMEOUT: 1013,
|
|
1003
|
+
LIMITED_AD: 1014,
|
|
1004
|
+
CONSENT_REQUIRED: 1015,
|
|
1005
|
+
TEST_MODE: 1016,
|
|
1006
|
+
INTERNAL_ERROR: 1017
|
|
1007
|
+
};
|
|
1008
|
+
var NON_ERROR_STATUSES = /* @__PURE__ */ new Set(["OK", "TEST_MODE", "NO_AD"]);
|
|
1009
|
+
var API_RESULT_ERROR_CODE_MAP = {
|
|
1010
|
+
HTTP_TIMEOUT: ERROR_CODES.HTTP_TIMEOUT,
|
|
1011
|
+
NETWORK_ERROR: ERROR_CODES.NETWORK_ERROR,
|
|
1012
|
+
EXECUTION_FAIL: ERROR_CODES.EXECUTION_FAIL,
|
|
1013
|
+
INTERRUPTED: ERROR_CODES.INTERRUPTED,
|
|
1014
|
+
INTERNAL_ERROR: ERROR_CODES.ERROR,
|
|
1015
|
+
FAIL: ERROR_CODES.FAIL
|
|
1016
|
+
};
|
|
1017
|
+
var AD_STATUS_ERROR_CODE_MAP = {
|
|
1018
|
+
BLOCKED: ERROR_CODES.BLOCKED,
|
|
1019
|
+
ERROR: ERROR_CODES.ERROR,
|
|
1020
|
+
TIMEOUT: ERROR_CODES.TIMEOUT,
|
|
1021
|
+
INVALID_SPACE: ERROR_CODES.INVALID_SPACE,
|
|
1022
|
+
INVALID_REQUEST: ERROR_CODES.INVALID_REQUEST,
|
|
1023
|
+
LIMITED_AD: ERROR_CODES.LIMITED_AD,
|
|
1024
|
+
CONSENT_REQUIRED: ERROR_CODES.CONSENT_REQUIRED
|
|
1025
|
+
};
|
|
1026
|
+
|
|
1027
|
+
// src/toss-ad/tossAdEventLog.ts
|
|
1028
|
+
var tossAdEventLog = createAsyncBridge("tossAdEventLog");
|
|
1029
|
+
|
|
1030
|
+
// src/toss-ad/errorTracking.ts
|
|
1031
|
+
var BANNER_ERROR_LOG_NAME = "display_ads_all::error__banner";
|
|
1032
|
+
function logBannerErrorToAppLog(info) {
|
|
1033
|
+
void tossAdEventLog({
|
|
1034
|
+
log_name: BANNER_ERROR_LOG_NAME,
|
|
1035
|
+
log_type: "debug",
|
|
1036
|
+
params: {
|
|
1037
|
+
error_code: info.errorCode,
|
|
1038
|
+
error_message: info.errorMessage,
|
|
1039
|
+
ad_group_id: info.adGroupId,
|
|
1040
|
+
mediation_id: info.mediationId,
|
|
1041
|
+
event_context_token: info.eventContextToken
|
|
1042
|
+
}
|
|
1043
|
+
}).catch(() => {
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
function trackBannerError(info) {
|
|
1047
|
+
if (info.endpointError) {
|
|
1048
|
+
void fetch(info.endpointError, {
|
|
1049
|
+
method: "POST",
|
|
1050
|
+
headers: { "Content-Type": "application/json" },
|
|
1051
|
+
body: JSON.stringify({
|
|
1052
|
+
eventContextToken: info.eventContextToken,
|
|
1053
|
+
mediationId: info.mediationId,
|
|
1054
|
+
errorCode: info.errorCode,
|
|
1055
|
+
errorMessage: info.errorMessage
|
|
1056
|
+
})
|
|
1057
|
+
// POST 실패(CORS·네트워크) → 앱로그 fallback (endpoint와 같은 채널이라 함께 막히는 경우 대비).
|
|
1058
|
+
}).catch(() => logBannerErrorToAppLog(info));
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
logBannerErrorToAppLog(info);
|
|
1062
|
+
}
|
|
1063
|
+
function trackResponseError(response, adGroupId, context) {
|
|
1064
|
+
if (response.resultType !== "SUCCESS") {
|
|
1065
|
+
trackBannerError({
|
|
1066
|
+
adGroupId,
|
|
1067
|
+
errorCode: API_RESULT_ERROR_CODE_MAP[response.resultType] ?? ERROR_CODES.INTERNAL_ERROR,
|
|
1068
|
+
errorMessage: response.error?.reason ?? response.resultType
|
|
1069
|
+
});
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
const success = response.success;
|
|
1073
|
+
if (!success || NON_ERROR_STATUSES.has(String(success.status))) {
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
trackBannerError({
|
|
1077
|
+
adGroupId,
|
|
1078
|
+
errorCode: AD_STATUS_ERROR_CODE_MAP[String(success.status)] ?? ERROR_CODES.INTERNAL_ERROR,
|
|
1079
|
+
mediationId: context.mediationId,
|
|
1080
|
+
eventContextToken: context.eventContextToken,
|
|
1081
|
+
errorMessage: `Ad response status: ${success.status}`,
|
|
1082
|
+
endpointError: context.errorEndpoint
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// src/toss-ad/eventTracking.ts
|
|
1087
|
+
async function handleTrackingRequest(request) {
|
|
1088
|
+
if (request.type === "EVENT_TRACKING") {
|
|
1089
|
+
await sendEventTracking(request);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
async function sendEventTracking(request) {
|
|
1093
|
+
if (request.urls.length === 0) {
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
const body = JSON.stringify({ type: request.eventName, data: request.eventPayload });
|
|
1097
|
+
const headers = { "Content-Type": "application/json" };
|
|
1098
|
+
if (request.requestId) {
|
|
1099
|
+
headers["X-Toss-RequestId"] = request.requestId;
|
|
1100
|
+
}
|
|
1101
|
+
const settled = await Promise.allSettled(
|
|
1102
|
+
request.urls.map(
|
|
1103
|
+
(url) => fetch(url, { method: "POST", headers, body }).then((response) => {
|
|
1104
|
+
if (!response.ok) {
|
|
1105
|
+
throw new Error(`HTTP ${response.status}`);
|
|
1106
|
+
}
|
|
1107
|
+
})
|
|
1108
|
+
)
|
|
1109
|
+
);
|
|
1110
|
+
const failedCount = settled.filter((result) => result.status === "rejected").length;
|
|
1111
|
+
if (failedCount === 0) {
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
trackBannerError({
|
|
1115
|
+
adGroupId: request.adGroupId,
|
|
1116
|
+
errorCode: ERROR_CODES.NETWORK_ERROR,
|
|
1117
|
+
errorMessage: `EVENT_TRACKING failed: ${request.eventName} (${failedCount}/${request.urls.length})`,
|
|
1118
|
+
mediationId: request.mediationId || void 0,
|
|
1119
|
+
eventContextToken: request.eventContextToken || void 0,
|
|
1120
|
+
endpointError: request.errorEndpoint
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// src/toss-ad/fetchAppsInTossAdSchema.ts
|
|
1125
|
+
var APPS_IN_TOSS_AD_SPEC_VERSION = "1.4.1";
|
|
1126
|
+
var FETCH_APPS_IN_TOSS_AD_DEVICE_MACRO = "{{device}}";
|
|
1127
|
+
var TOSS_APP_BUNDLE_IDS = {
|
|
1128
|
+
ios: "com.vivarepublica.cash",
|
|
1129
|
+
android: "viva.republica.toss"
|
|
1130
|
+
};
|
|
1131
|
+
function resolveTossAppBundleId(os) {
|
|
1132
|
+
return TOSS_APP_BUNDLE_IDS[os];
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// src/toss-ad/normalize.ts
|
|
1136
|
+
var LIST_BANNER_STYLE_ID = "1";
|
|
1137
|
+
var NATIVE_IMAGE_STYLE_ID = "2";
|
|
1138
|
+
var AVAILABLE_STYLE_IDS = [LIST_BANNER_STYLE_ID, NATIVE_IMAGE_STYLE_ID];
|
|
1139
|
+
var SUPPORTED_STYLE_IDS = new Set(AVAILABLE_STYLE_IDS);
|
|
1140
|
+
function normalizeAdResponse(adResponse) {
|
|
1141
|
+
const ads = Array.isArray(adResponse.ads) ? adResponse.ads.filter((ad) => SUPPORTED_STYLE_IDS.has(String(ad.styleId))) : [];
|
|
1142
|
+
return {
|
|
1143
|
+
requestId: adResponse.requestId ?? "",
|
|
1144
|
+
// SDK가 event 트래킹 위임(onTrackingRequest) 시 응답 top-level에서 읽어 실어 보낸다.
|
|
1145
|
+
// 떼어내면 SDK가 토큰을 못 보고, event 트래킹 실패 시 endpoint.error에서 토큰이 빠진다.
|
|
1146
|
+
eventContextToken: adResponse.eventContextToken,
|
|
1147
|
+
status: adResponse.status ?? "OK",
|
|
1148
|
+
ads,
|
|
1149
|
+
ext: adResponse.ext
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
function normalizeApiResponse(raw) {
|
|
1153
|
+
if (isApiResponse(raw)) {
|
|
1154
|
+
if (raw.resultType !== "SUCCESS") {
|
|
1155
|
+
return raw;
|
|
1156
|
+
}
|
|
1157
|
+
if (!raw.success) {
|
|
1158
|
+
return {
|
|
1159
|
+
resultType: "FAIL",
|
|
1160
|
+
error: { reason: "fetchTossAd returned SUCCESS without payload" }
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
return { ...raw, success: normalizeAdResponse(raw.success) };
|
|
1164
|
+
}
|
|
1165
|
+
if (isAdResponse(raw)) {
|
|
1166
|
+
return {
|
|
1167
|
+
resultType: "SUCCESS",
|
|
1168
|
+
success: normalizeAdResponse(raw)
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
return { resultType: "FAIL", error: { reason: "Invalid response from fetchTossAd" } };
|
|
1172
|
+
}
|
|
1173
|
+
function isApiResponse(payload) {
|
|
1174
|
+
return Boolean(payload && typeof payload === "object" && "resultType" in payload);
|
|
1175
|
+
}
|
|
1176
|
+
function isAdResponse(payload) {
|
|
1177
|
+
return Boolean(payload && typeof payload === "object" && "ads" in payload);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
989
1180
|
// src/toss-ad/opener.ts
|
|
990
1181
|
var openURL = createAsyncBridge("openURL");
|
|
991
1182
|
function openUrlOpener(url) {
|
|
@@ -1000,8 +1191,49 @@ function supertossWeb(uri) {
|
|
|
1000
1191
|
return `supertoss://web?url=${encodeURIComponent(uri)}&external=true`;
|
|
1001
1192
|
}
|
|
1002
1193
|
|
|
1194
|
+
// src/toss-ad/resultTracking.ts
|
|
1195
|
+
async function fireResultTracking(response, adGroupId, ctx) {
|
|
1196
|
+
const status = response.success?.status;
|
|
1197
|
+
if (response.resultType !== "SUCCESS" || status !== "OK" && status !== "TEST_MODE") {
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
if (ctx.resultEndpoint == null || ctx.mediationId == null) {
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
const body = {
|
|
1204
|
+
mediationId: ctx.mediationId,
|
|
1205
|
+
spaceUnitId: adGroupId,
|
|
1206
|
+
requestId: response.success?.requestId ?? "",
|
|
1207
|
+
eventContextToken: ctx.eventContextToken,
|
|
1208
|
+
winnerSource: "TOSS",
|
|
1209
|
+
content: null,
|
|
1210
|
+
adUnitId: ctx.adUnitId ?? null,
|
|
1211
|
+
placementId: ctx.placementId ?? null,
|
|
1212
|
+
tossFailed: null
|
|
1213
|
+
};
|
|
1214
|
+
try {
|
|
1215
|
+
const res = await fetch(ctx.resultEndpoint, {
|
|
1216
|
+
method: "POST",
|
|
1217
|
+
headers: { "Content-Type": "application/json" },
|
|
1218
|
+
body: JSON.stringify(body)
|
|
1219
|
+
});
|
|
1220
|
+
if (!res.ok) {
|
|
1221
|
+
throw new Error(`HTTP ${res.status}`);
|
|
1222
|
+
}
|
|
1223
|
+
} catch (error) {
|
|
1224
|
+
trackBannerError({
|
|
1225
|
+
adGroupId,
|
|
1226
|
+
errorCode: ERROR_CODES.NETWORK_ERROR,
|
|
1227
|
+
errorMessage: `RESULT_TRACKING failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1228
|
+
mediationId: ctx.mediationId,
|
|
1229
|
+
eventContextToken: ctx.eventContextToken,
|
|
1230
|
+
endpointError: ctx.errorEndpoint
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1003
1235
|
// src/toss-ad/scriptLoader.ts
|
|
1004
|
-
var DEFAULT_SDK_URL = "https://static.toss.im/ads/sdk/toss-ads-space-kit-1.
|
|
1236
|
+
var DEFAULT_SDK_URL = "https://static.toss.im/ads/sdk/toss-ads-space-kit-3.1.0.js";
|
|
1005
1237
|
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
1006
1238
|
var pendingLoad = null;
|
|
1007
1239
|
function getAdsSdk() {
|
|
@@ -1057,12 +1289,79 @@ function loadAdsSdk() {
|
|
|
1057
1289
|
return promise;
|
|
1058
1290
|
}
|
|
1059
1291
|
|
|
1292
|
+
// package.json
|
|
1293
|
+
var package_default = {
|
|
1294
|
+
name: "@apps-in-toss/web-bridge",
|
|
1295
|
+
type: "module",
|
|
1296
|
+
version: "2.10.0",
|
|
1297
|
+
description: "Web Bridge for Apps In Toss",
|
|
1298
|
+
scripts: {
|
|
1299
|
+
typecheck: "tsc --noEmit",
|
|
1300
|
+
lint: "eslint .",
|
|
1301
|
+
build: "tsup"
|
|
1302
|
+
},
|
|
1303
|
+
main: "./dist/index.cjs",
|
|
1304
|
+
module: "./dist/index.js",
|
|
1305
|
+
types: "./dist/index.d.ts",
|
|
1306
|
+
exports: {
|
|
1307
|
+
".": {
|
|
1308
|
+
import: {
|
|
1309
|
+
types: "./dist/index.d.ts",
|
|
1310
|
+
default: "./dist/index.js"
|
|
1311
|
+
},
|
|
1312
|
+
require: {
|
|
1313
|
+
types: "./dist/index.d.cts",
|
|
1314
|
+
default: "./dist/index.cjs"
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
},
|
|
1318
|
+
files: [
|
|
1319
|
+
"dist"
|
|
1320
|
+
],
|
|
1321
|
+
dependencies: {
|
|
1322
|
+
"@apps-in-toss/types": "workspace:*"
|
|
1323
|
+
},
|
|
1324
|
+
devDependencies: {
|
|
1325
|
+
"@apps-in-toss/bridge-core": "workspace:*",
|
|
1326
|
+
"@apps-in-toss/native-modules": "workspace:^",
|
|
1327
|
+
"@swc/core": "^1.12.7",
|
|
1328
|
+
picocolors: "^1.1.1",
|
|
1329
|
+
"ts-morph": "^26.0.0",
|
|
1330
|
+
tsup: "^8.5.0",
|
|
1331
|
+
typescript: "5.8.3",
|
|
1332
|
+
vitest: "^3.1.2"
|
|
1333
|
+
}
|
|
1334
|
+
};
|
|
1335
|
+
|
|
1336
|
+
// src/toss-ad/sdkVersion.ts
|
|
1337
|
+
var SDK_VERSION = package_default.version;
|
|
1338
|
+
|
|
1339
|
+
// src/toss-ad/trackingContext.ts
|
|
1340
|
+
function nonEmptyString(value) {
|
|
1341
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1342
|
+
}
|
|
1343
|
+
function extractAdTrackingContext(response) {
|
|
1344
|
+
const mediation = response?.ext?.mediation;
|
|
1345
|
+
return {
|
|
1346
|
+
mediationId: nonEmptyString(mediation?.mediationId),
|
|
1347
|
+
eventContextToken: nonEmptyString(response?.eventContextToken),
|
|
1348
|
+
resultEndpoint: nonEmptyString(mediation?.endpoint?.result),
|
|
1349
|
+
errorEndpoint: nonEmptyString(mediation?.endpoint?.error),
|
|
1350
|
+
adUnitId: nonEmptyString(mediation?.admob?.adUnitId),
|
|
1351
|
+
placementId: typeof mediation?.admob?.placementId === "number" ? mediation.admob.placementId : void 0
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1060
1355
|
// src/toss-ad/index.ts
|
|
1061
1356
|
var fetchTossAd = Object.assign(createEventBridge("fetchTossAd"), {
|
|
1062
1357
|
isSupported: createConstantBridge("fetchTossAd_isSupported")
|
|
1063
1358
|
});
|
|
1064
|
-
var
|
|
1065
|
-
|
|
1359
|
+
var fetchAppsInTossAd = Object.assign(createEventBridge("fetchAppsInTossAd"), {
|
|
1360
|
+
isSupported: createConstantBridge("fetchAppsInTossAd_isSupported")
|
|
1361
|
+
});
|
|
1362
|
+
var getPlatformOS = createConstantBridge("getPlatformOS");
|
|
1363
|
+
var getTossAppVersion = createConstantBridge("getTossAppVersion");
|
|
1364
|
+
var WEB_SDK_ID = "108";
|
|
1066
1365
|
var INVALID_AD_GROUP_ID_ERROR_MESSAGE = "\uC798\uBABB\uB41C \uC694\uCCAD\uC774\uC5D0\uC694. \uD544\uC694\uD55C \uAC12\uC774 \uBE44\uC5B4 \uC788\uC5B4\uC694.";
|
|
1067
1366
|
function normalizeAdGroupId(adGroupId) {
|
|
1068
1367
|
return adGroupId.trim();
|
|
@@ -1099,62 +1398,56 @@ function fetchTossAdPromise(options) {
|
|
|
1099
1398
|
});
|
|
1100
1399
|
});
|
|
1101
1400
|
}
|
|
1102
|
-
function
|
|
1103
|
-
|
|
1104
|
-
return {
|
|
1105
|
-
requestId: adResponse.requestId ?? "",
|
|
1106
|
-
status: adResponse.status ?? "OK",
|
|
1107
|
-
ads,
|
|
1108
|
-
ext: adResponse.ext
|
|
1109
|
-
};
|
|
1401
|
+
function getSubBundle() {
|
|
1402
|
+
return globalThis.__granite?.app?.name ?? "";
|
|
1110
1403
|
}
|
|
1111
|
-
function
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1404
|
+
function fetchAppsInTossAdPromise(spaceUnitId) {
|
|
1405
|
+
const platformOS = getPlatformOS();
|
|
1406
|
+
const tossAppVersion = getTossAppVersion();
|
|
1407
|
+
const body = {
|
|
1408
|
+
specVersion: APPS_IN_TOSS_AD_SPEC_VERSION,
|
|
1409
|
+
platform: "WEB",
|
|
1410
|
+
spaceUnitId,
|
|
1411
|
+
sdkVersion: SDK_VERSION,
|
|
1412
|
+
availableStyleIds: AVAILABLE_STYLE_IDS,
|
|
1413
|
+
// device는 매크로키만 보내고 native가 실제 device로 치환한다.
|
|
1414
|
+
device: FETCH_APPS_IN_TOSS_AD_DEVICE_MACRO,
|
|
1415
|
+
app: {
|
|
1416
|
+
version: tossAppVersion,
|
|
1417
|
+
bundle: resolveTossAppBundleId(platformOS),
|
|
1418
|
+
subBundle: getSubBundle()
|
|
1121
1419
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
resultType: "SUCCESS",
|
|
1127
|
-
success: normalizeAdResponse(raw)
|
|
1128
|
-
};
|
|
1129
|
-
}
|
|
1130
|
-
return { resultType: "FAIL", error: { reason: "Invalid response from fetchTossAd" } };
|
|
1131
|
-
}
|
|
1132
|
-
function isApiResponse(payload) {
|
|
1133
|
-
return Boolean(payload && typeof payload === "object" && "resultType" in payload);
|
|
1134
|
-
}
|
|
1135
|
-
function isAdResponse(payload) {
|
|
1136
|
-
return Boolean(payload && typeof payload === "object" && "ads" in payload);
|
|
1420
|
+
};
|
|
1421
|
+
return new Promise((resolve, reject) => {
|
|
1422
|
+
fetchAppsInTossAd({ options: body, onEvent: resolve, onError: reject });
|
|
1423
|
+
});
|
|
1137
1424
|
}
|
|
1138
1425
|
function createCustomAdFetcher() {
|
|
1139
1426
|
return async (_endpoint, request) => {
|
|
1140
1427
|
const spaceUnitId = normalizeAdGroupId(request.spaceUnitId);
|
|
1141
1428
|
if (spaceUnitId.length === 0) {
|
|
1429
|
+
trackBannerError({
|
|
1430
|
+
adGroupId: spaceUnitId,
|
|
1431
|
+
errorCode: ERROR_CODES.INVALID_SPACE,
|
|
1432
|
+
errorMessage: INVALID_AD_GROUP_ID_ERROR_MESSAGE
|
|
1433
|
+
});
|
|
1142
1434
|
return createInvalidAdGroupIdResponse();
|
|
1143
1435
|
}
|
|
1144
1436
|
try {
|
|
1145
|
-
const raw = await fetchTossAdPromise({
|
|
1437
|
+
const raw = fetchAppsInTossAd.isSupported() ? await fetchAppsInTossAdPromise(spaceUnitId) : await fetchTossAdPromise({
|
|
1146
1438
|
adGroupId: spaceUnitId,
|
|
1147
|
-
sdkId:
|
|
1148
|
-
availableStyleIds:
|
|
1439
|
+
sdkId: WEB_SDK_ID,
|
|
1440
|
+
availableStyleIds: AVAILABLE_STYLE_IDS
|
|
1149
1441
|
});
|
|
1150
|
-
|
|
1442
|
+
const response = normalizeApiResponse(raw);
|
|
1443
|
+
const trackingContext = extractAdTrackingContext(response.success);
|
|
1444
|
+
trackResponseError(response, spaceUnitId, trackingContext);
|
|
1445
|
+
void fireResultTracking(response, spaceUnitId, trackingContext);
|
|
1446
|
+
return response;
|
|
1151
1447
|
} catch (error) {
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
reason: error instanceof Error ? error.message : "Unknown fetchTossAd error"
|
|
1156
|
-
}
|
|
1157
|
-
};
|
|
1448
|
+
const reason = error instanceof Error ? error.message : "Unknown fetchTossAd error";
|
|
1449
|
+
trackBannerError({ adGroupId: spaceUnitId, errorCode: ERROR_CODES.NETWORK_ERROR, errorMessage: reason });
|
|
1450
|
+
return { resultType: "FAIL", error: { reason } };
|
|
1158
1451
|
}
|
|
1159
1452
|
};
|
|
1160
1453
|
}
|
|
@@ -1180,7 +1473,12 @@ function initialize(options) {
|
|
|
1180
1473
|
return;
|
|
1181
1474
|
}
|
|
1182
1475
|
const customAdFetcher = createCustomAdFetcher();
|
|
1183
|
-
const config = {
|
|
1476
|
+
const config = {
|
|
1477
|
+
environment: "live",
|
|
1478
|
+
customAdFetcher,
|
|
1479
|
+
opener: openUrlOpener,
|
|
1480
|
+
onTrackingRequest: handleTrackingRequest
|
|
1481
|
+
};
|
|
1184
1482
|
sdk.init(config);
|
|
1185
1483
|
});
|
|
1186
1484
|
pendingLoad2 = initPromise;
|
|
@@ -1357,7 +1655,7 @@ function attachBanner(adGroupId, target, options = {}) {
|
|
|
1357
1655
|
autoLoad: true,
|
|
1358
1656
|
theme: theme === "auto" ? void 0 : theme,
|
|
1359
1657
|
renderPadding: (styleId) => {
|
|
1360
|
-
if (styleId ===
|
|
1658
|
+
if (styleId === LIST_BANNER_STYLE_ID) {
|
|
1361
1659
|
return DEFAULT_ATTACH_PADDING_BANNER;
|
|
1362
1660
|
}
|
|
1363
1661
|
return DEFAULT_ATTACH_PADDING_NATIVE_IMAGE;
|
|
@@ -1430,6 +1728,8 @@ function wrapCallbacks(adGroupId, callbacks) {
|
|
|
1430
1728
|
});
|
|
1431
1729
|
callbacks.onAdImpression?.(mapEvent(payload));
|
|
1432
1730
|
},
|
|
1731
|
+
// 에러 트래킹은 응답을 소유한 createCustomAdFetcher가 담당한다. 콜백은 publisher 전달만.
|
|
1732
|
+
// 응답 수신 후 에러의 mediationId는 SDK(ads-sdk/web)가 error에 실어주므로 그대로 전달한다.
|
|
1433
1733
|
onAdFailedToRender: (payload) => callbacks.onAdFailedToRender?.({
|
|
1434
1734
|
...mapEvent(payload),
|
|
1435
1735
|
error: payload?.error ?? { code: 0, message: "UNKNOWN" }
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apps-in-toss/web-bridge",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.10.0",
|
|
5
5
|
"description": "Web Bridge for Apps In Toss",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"typecheck": "tsc --noEmit",
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
"dist"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@apps-in-toss/types": "2.
|
|
30
|
+
"@apps-in-toss/types": "2.10.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@apps-in-toss/bridge-core": "2.
|
|
34
|
-
"@apps-in-toss/native-modules": "^2.
|
|
33
|
+
"@apps-in-toss/bridge-core": "2.10.0",
|
|
34
|
+
"@apps-in-toss/native-modules": "^2.10.0",
|
|
35
35
|
"@swc/core": "^1.12.7",
|
|
36
36
|
"picocolors": "^1.1.1",
|
|
37
37
|
"ts-morph": "^26.0.0",
|