@apps-in-toss/web-bridge 2.4.0 → 2.4.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/dist/index.cjs CHANGED
@@ -1006,6 +1006,34 @@ var showFullScreenAd = Object.assign(
1006
1006
  }
1007
1007
  );
1008
1008
 
1009
+ // src/toss-ad/attachBannerRegistry.ts
1010
+ var attachedBannerRegistry = /* @__PURE__ */ new WeakMap();
1011
+ function getOrCreateAttachedBanner(element, createAttachedBanner) {
1012
+ const attachedBanner = attachedBannerRegistry.get(element);
1013
+ if (attachedBanner) {
1014
+ return attachedBanner;
1015
+ }
1016
+ const createdAttachedBanner = createAttachedBanner();
1017
+ let isDestroyed = false;
1018
+ const registeredAttachedBanner = {
1019
+ destroy() {
1020
+ if (isDestroyed) {
1021
+ return;
1022
+ }
1023
+ isDestroyed = true;
1024
+ if (attachedBannerRegistry.get(element) === registeredAttachedBanner) {
1025
+ attachedBannerRegistry.delete(element);
1026
+ }
1027
+ createdAttachedBanner.destroy();
1028
+ }
1029
+ };
1030
+ attachedBannerRegistry.set(element, registeredAttachedBanner);
1031
+ return registeredAttachedBanner;
1032
+ }
1033
+ function resetAttachedBannerRegistry() {
1034
+ attachedBannerRegistry = /* @__PURE__ */ new WeakMap();
1035
+ }
1036
+
1009
1037
  // src/toss-ad/opener.ts
1010
1038
  var openURL = createAsyncBridge("openURL");
1011
1039
  function openUrlOpener(url) {
@@ -1083,14 +1111,37 @@ var fetchTossAd = Object.assign(createEventBridge("fetchTossAd"), {
1083
1111
  });
1084
1112
  var tossAdEventLog = createAsyncBridge("tossAdEventLog");
1085
1113
  var SUPPORTED_STYLE_IDS = /* @__PURE__ */ new Set(["1", "2"]);
1114
+ 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.";
1115
+ function normalizeAdGroupId(adGroupId) {
1116
+ return adGroupId.trim();
1117
+ }
1118
+ function createInvalidAdGroupIdError() {
1119
+ return new Error(INVALID_AD_GROUP_ID_ERROR_MESSAGE);
1120
+ }
1121
+ function createInvalidAdGroupIdResponse() {
1122
+ return {
1123
+ resultType: "FAIL",
1124
+ error: {
1125
+ reason: INVALID_AD_GROUP_ID_ERROR_MESSAGE
1126
+ }
1127
+ };
1128
+ }
1086
1129
  function fetchTossAdPromise(options) {
1087
1130
  return new Promise((resolve, reject) => {
1088
1131
  if (!fetchTossAd.isSupported()) {
1089
1132
  reject(new Error("fetchTossAd is not supported in this environment."));
1090
1133
  return;
1091
1134
  }
1135
+ const adGroupId = normalizeAdGroupId(options.adGroupId);
1136
+ if (adGroupId.length === 0) {
1137
+ reject(createInvalidAdGroupIdError());
1138
+ return;
1139
+ }
1092
1140
  return fetchTossAd({
1093
- options,
1141
+ options: {
1142
+ ...options,
1143
+ adGroupId
1144
+ },
1094
1145
  onEvent: resolve,
1095
1146
  onError: reject
1096
1147
  });
@@ -1134,9 +1185,13 @@ function isAdResponse(payload) {
1134
1185
  }
1135
1186
  function createCustomAdFetcher() {
1136
1187
  return async (_endpoint, request) => {
1188
+ const spaceUnitId = normalizeAdGroupId(request.spaceUnitId);
1189
+ if (spaceUnitId.length === 0) {
1190
+ return createInvalidAdGroupIdResponse();
1191
+ }
1137
1192
  try {
1138
1193
  const raw = await fetchTossAdPromise({
1139
- adGroupId: request.spaceUnitId,
1194
+ adGroupId: spaceUnitId,
1140
1195
  sdkId: "108",
1141
1196
  availableStyleIds: ["1", "2"]
1142
1197
  });
@@ -1185,17 +1240,21 @@ function initialize(options) {
1185
1240
  }
1186
1241
  function attach(adGroupId, target, options = {}) {
1187
1242
  const { callbacks } = options;
1243
+ const normalizedAdGroupId = normalizeAdGroupId(adGroupId);
1188
1244
  const rejectAttached = (error) => {
1189
1245
  const normalizedError = error instanceof Error ? error : new Error(String(error));
1190
1246
  callbacks?.onAdFailedToRender?.({
1191
1247
  slotId: "",
1192
- adGroupId,
1248
+ adGroupId: normalizedAdGroupId,
1193
1249
  adMetadata: {},
1194
1250
  error: { code: 0, message: normalizedError.message }
1195
1251
  });
1196
1252
  };
1197
1253
  try {
1198
- const spaceId = adGroupId;
1254
+ const spaceId = normalizedAdGroupId;
1255
+ if (spaceId.length === 0) {
1256
+ throw createInvalidAdGroupIdError();
1257
+ }
1199
1258
  const sdk = getAdsSdk();
1200
1259
  if (!sdk) {
1201
1260
  throw new Error("[toss-ad] Call initialize() before attaching an ad.");
@@ -1212,7 +1271,7 @@ function attach(adGroupId, target, options = {}) {
1212
1271
  autoLoad: true,
1213
1272
  theme: options.theme,
1214
1273
  padding: options.padding,
1215
- callbacks: wrapCallbacks(adGroupId, options.callbacks)
1274
+ callbacks: wrapCallbacks(normalizedAdGroupId, options.callbacks)
1216
1275
  };
1217
1276
  sdk.banner.createSlot(element, slotOptions);
1218
1277
  } catch (error) {
@@ -1226,6 +1285,7 @@ var DEFAULT_ATTACH_TONE = "blackAndWhite";
1226
1285
  var DEFAULT_ATTACH_VARIANT = "expanded";
1227
1286
  var ATTACH_CLASS_NAME = "toss-ads-attach";
1228
1287
  var ATTACH_STYLE_ID = "toss-ads-attach-style";
1288
+ var ATTACH_WRAPPER_ATTRIBUTE = "data-toss-ads-attach-banner-wrapper";
1229
1289
  function ensureAttachStyles(container) {
1230
1290
  const documentRef = container.ownerDocument;
1231
1291
  if (!documentRef) {
@@ -1254,6 +1314,13 @@ function ensureAttachStyles(container) {
1254
1314
  }
1255
1315
  styleContainer.appendChild(style);
1256
1316
  }
1317
+ function removeStaleAttachedBannerWrappers(container) {
1318
+ Array.from(container.children).forEach((child) => {
1319
+ if (child instanceof HTMLElement && child.getAttribute(ATTACH_WRAPPER_ATTRIBUTE) === "true") {
1320
+ container.removeChild(child);
1321
+ }
1322
+ });
1323
+ }
1257
1324
  function attachBanner(adGroupId, target, options = {}) {
1258
1325
  const {
1259
1326
  callbacks,
@@ -1261,46 +1328,22 @@ function attachBanner(adGroupId, target, options = {}) {
1261
1328
  tone = DEFAULT_ATTACH_TONE,
1262
1329
  variant = DEFAULT_ATTACH_VARIANT
1263
1330
  } = options;
1264
- const wrapper = document.createElement("div");
1265
- wrapper.style.width = "100%";
1266
- wrapper.style.height = "100%";
1267
- wrapper.style.boxSizing = "border-box";
1268
- wrapper.style.display = "flex";
1269
- wrapper.style.flexDirection = "column";
1270
- wrapper.style.justifyContent = "center";
1271
- wrapper.style.overflow = "hidden";
1272
- if (variant === "card") {
1273
- wrapper.style.padding = "0 10px";
1274
- }
1275
- const element = document.createElement("div");
1276
- element.classList.add(ATTACH_CLASS_NAME);
1277
- if (tone === "grey") {
1278
- element.classList.add("toss-ads-tone-grey");
1279
- }
1280
- if (theme === "light") {
1281
- element.classList.add("toss-ads-theme-light");
1282
- } else if (theme === "dark") {
1283
- element.classList.add("toss-ads-theme-dark");
1284
- }
1285
- if (variant === "card") {
1286
- element.style.borderRadius = "16px";
1287
- element.style.overflow = "hidden";
1288
- }
1289
- wrapper.appendChild(element);
1290
- let isAttached = false;
1291
- let slot = null;
1331
+ const normalizedAdGroupId = normalizeAdGroupId(adGroupId);
1292
1332
  const rejectAttached = (error) => {
1293
1333
  const normalizedError = error instanceof Error ? error : new Error(String(error));
1294
1334
  callbacks?.onAdFailedToRender?.({
1295
1335
  slotId: "",
1296
- adGroupId,
1336
+ adGroupId: normalizedAdGroupId,
1297
1337
  adMetadata: {},
1298
1338
  error: { code: 0, message: normalizedError.message }
1299
1339
  });
1300
1340
  };
1301
- const wrappedCallbacks = wrapCallbacks(adGroupId, callbacks);
1341
+ const wrappedCallbacks = wrapCallbacks(normalizedAdGroupId, callbacks);
1302
1342
  try {
1303
- const spaceId = adGroupId;
1343
+ const spaceId = normalizedAdGroupId;
1344
+ if (spaceId.length === 0) {
1345
+ throw createInvalidAdGroupIdError();
1346
+ }
1304
1347
  const sdk = getAdsSdk();
1305
1348
  if (!sdk) {
1306
1349
  throw new Error("[toss-ad] Call initialize() before attaching an ad.");
@@ -1312,37 +1355,77 @@ function attachBanner(adGroupId, target, options = {}) {
1312
1355
  if (!container) {
1313
1356
  throw new Error(`[toss-ad] Failed to find target element: ${target}`);
1314
1357
  }
1315
- ensureAttachStyles(container);
1316
- container.appendChild(wrapper);
1317
- isAttached = true;
1318
- const slotOptions = {
1319
- spaceId,
1320
- autoLoad: true,
1321
- theme: theme === "auto" ? void 0 : theme,
1322
- renderPadding: (styleId) => {
1323
- if (styleId === "1") {
1324
- return DEFAULT_ATTACH_PADDING_BANNER;
1325
- } else {
1326
- return DEFAULT_ATTACH_PADDING_NATIVE_IMAGE;
1358
+ return getOrCreateAttachedBanner(container, () => {
1359
+ const wrapper = document.createElement("div");
1360
+ wrapper.style.width = "100%";
1361
+ wrapper.style.height = "100%";
1362
+ wrapper.style.boxSizing = "border-box";
1363
+ wrapper.style.display = "flex";
1364
+ wrapper.style.flexDirection = "column";
1365
+ wrapper.style.justifyContent = "center";
1366
+ wrapper.style.overflow = "hidden";
1367
+ wrapper.setAttribute(ATTACH_WRAPPER_ATTRIBUTE, "true");
1368
+ if (variant === "card") {
1369
+ wrapper.style.padding = "0 10px";
1370
+ }
1371
+ const element = document.createElement("div");
1372
+ element.classList.add(ATTACH_CLASS_NAME);
1373
+ if (tone === "grey") {
1374
+ element.classList.add("toss-ads-tone-grey");
1375
+ }
1376
+ if (theme === "light") {
1377
+ element.classList.add("toss-ads-theme-light");
1378
+ } else if (theme === "dark") {
1379
+ element.classList.add("toss-ads-theme-dark");
1380
+ }
1381
+ if (variant === "card") {
1382
+ element.style.borderRadius = "16px";
1383
+ element.style.overflow = "hidden";
1384
+ }
1385
+ wrapper.appendChild(element);
1386
+ let isAttached = false;
1387
+ let slot = null;
1388
+ try {
1389
+ ensureAttachStyles(container);
1390
+ removeStaleAttachedBannerWrappers(container);
1391
+ container.appendChild(wrapper);
1392
+ isAttached = true;
1393
+ const slotOptions = {
1394
+ spaceId,
1395
+ autoLoad: true,
1396
+ theme: theme === "auto" ? void 0 : theme,
1397
+ renderPadding: (styleId) => {
1398
+ if (styleId === "1") {
1399
+ return DEFAULT_ATTACH_PADDING_BANNER;
1400
+ }
1401
+ return DEFAULT_ATTACH_PADDING_NATIVE_IMAGE;
1402
+ },
1403
+ callbacks: wrappedCallbacks
1404
+ };
1405
+ slot = sdk.banner.createSlot(element, slotOptions);
1406
+ } catch (error) {
1407
+ if (isAttached && wrapper.parentNode) {
1408
+ wrapper.parentNode.removeChild(wrapper);
1327
1409
  }
1328
- },
1329
- callbacks: wrappedCallbacks
1330
- };
1331
- slot = sdk.banner.createSlot(element, slotOptions);
1410
+ throw error;
1411
+ }
1412
+ return {
1413
+ destroy() {
1414
+ slot?.destroy();
1415
+ if (isAttached && wrapper.parentNode) {
1416
+ wrapper.parentNode.removeChild(wrapper);
1417
+ }
1418
+ isAttached = false;
1419
+ }
1420
+ };
1421
+ });
1332
1422
  } catch (error) {
1333
- if (isAttached && wrapper.parentNode) {
1334
- wrapper.parentNode.removeChild(wrapper);
1335
- }
1336
1423
  rejectAttached(error);
1337
- }
1338
- return {
1339
- destroy() {
1340
- slot?.destroy();
1341
- if (isAttached && wrapper.parentNode) {
1342
- wrapper.parentNode.removeChild(wrapper);
1424
+ return {
1425
+ destroy() {
1343
1426
  }
1344
- }
1345
- };
1427
+ };
1428
+ }
1346
1429
  }
1347
1430
  function destroy(slotId) {
1348
1431
  const sdk = getAdsSdk();
@@ -1357,6 +1440,7 @@ function destroyAll() {
1357
1440
  return;
1358
1441
  }
1359
1442
  sdk.banner.destroyAll();
1443
+ resetAttachedBannerRegistry();
1360
1444
  }
1361
1445
  function wrapCallbacks(adGroupId, callbacks) {
1362
1446
  if (!callbacks) {
package/dist/index.d.cts CHANGED
@@ -1022,6 +1022,9 @@ interface AttachBannerResult {
1022
1022
  }
1023
1023
 
1024
1024
  declare function initialize(options: InitializeOptions): void;
1025
+ /**
1026
+ * @deprecated attach는 더 이상 권장되지 않습니다. attachBanner를 사용해주세요.
1027
+ */
1025
1028
  declare function attach(adGroupId: string, target: string | HTMLElement, options?: AttachOptions): void;
1026
1029
  declare function attachBanner(adGroupId: string, target: string | HTMLElement, options?: AttachBannerOptions): AttachBannerResult;
1027
1030
  declare function destroy(slotId: string): void;
package/dist/index.d.ts CHANGED
@@ -1022,6 +1022,9 @@ interface AttachBannerResult {
1022
1022
  }
1023
1023
 
1024
1024
  declare function initialize(options: InitializeOptions): void;
1025
+ /**
1026
+ * @deprecated attach는 더 이상 권장되지 않습니다. attachBanner를 사용해주세요.
1027
+ */
1025
1028
  declare function attach(adGroupId: string, target: string | HTMLElement, options?: AttachOptions): void;
1026
1029
  declare function attachBanner(adGroupId: string, target: string | HTMLElement, options?: AttachBannerOptions): AttachBannerResult;
1027
1030
  declare function destroy(slotId: string): void;
package/dist/index.js CHANGED
@@ -958,6 +958,34 @@ var showFullScreenAd = Object.assign(
958
958
  }
959
959
  );
960
960
 
961
+ // src/toss-ad/attachBannerRegistry.ts
962
+ var attachedBannerRegistry = /* @__PURE__ */ new WeakMap();
963
+ function getOrCreateAttachedBanner(element, createAttachedBanner) {
964
+ const attachedBanner = attachedBannerRegistry.get(element);
965
+ if (attachedBanner) {
966
+ return attachedBanner;
967
+ }
968
+ const createdAttachedBanner = createAttachedBanner();
969
+ let isDestroyed = false;
970
+ const registeredAttachedBanner = {
971
+ destroy() {
972
+ if (isDestroyed) {
973
+ return;
974
+ }
975
+ isDestroyed = true;
976
+ if (attachedBannerRegistry.get(element) === registeredAttachedBanner) {
977
+ attachedBannerRegistry.delete(element);
978
+ }
979
+ createdAttachedBanner.destroy();
980
+ }
981
+ };
982
+ attachedBannerRegistry.set(element, registeredAttachedBanner);
983
+ return registeredAttachedBanner;
984
+ }
985
+ function resetAttachedBannerRegistry() {
986
+ attachedBannerRegistry = /* @__PURE__ */ new WeakMap();
987
+ }
988
+
961
989
  // src/toss-ad/opener.ts
962
990
  var openURL = createAsyncBridge("openURL");
963
991
  function openUrlOpener(url) {
@@ -1035,14 +1063,37 @@ var fetchTossAd = Object.assign(createEventBridge("fetchTossAd"), {
1035
1063
  });
1036
1064
  var tossAdEventLog = createAsyncBridge("tossAdEventLog");
1037
1065
  var SUPPORTED_STYLE_IDS = /* @__PURE__ */ new Set(["1", "2"]);
1066
+ 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
+ function normalizeAdGroupId(adGroupId) {
1068
+ return adGroupId.trim();
1069
+ }
1070
+ function createInvalidAdGroupIdError() {
1071
+ return new Error(INVALID_AD_GROUP_ID_ERROR_MESSAGE);
1072
+ }
1073
+ function createInvalidAdGroupIdResponse() {
1074
+ return {
1075
+ resultType: "FAIL",
1076
+ error: {
1077
+ reason: INVALID_AD_GROUP_ID_ERROR_MESSAGE
1078
+ }
1079
+ };
1080
+ }
1038
1081
  function fetchTossAdPromise(options) {
1039
1082
  return new Promise((resolve, reject) => {
1040
1083
  if (!fetchTossAd.isSupported()) {
1041
1084
  reject(new Error("fetchTossAd is not supported in this environment."));
1042
1085
  return;
1043
1086
  }
1087
+ const adGroupId = normalizeAdGroupId(options.adGroupId);
1088
+ if (adGroupId.length === 0) {
1089
+ reject(createInvalidAdGroupIdError());
1090
+ return;
1091
+ }
1044
1092
  return fetchTossAd({
1045
- options,
1093
+ options: {
1094
+ ...options,
1095
+ adGroupId
1096
+ },
1046
1097
  onEvent: resolve,
1047
1098
  onError: reject
1048
1099
  });
@@ -1086,9 +1137,13 @@ function isAdResponse(payload) {
1086
1137
  }
1087
1138
  function createCustomAdFetcher() {
1088
1139
  return async (_endpoint, request) => {
1140
+ const spaceUnitId = normalizeAdGroupId(request.spaceUnitId);
1141
+ if (spaceUnitId.length === 0) {
1142
+ return createInvalidAdGroupIdResponse();
1143
+ }
1089
1144
  try {
1090
1145
  const raw = await fetchTossAdPromise({
1091
- adGroupId: request.spaceUnitId,
1146
+ adGroupId: spaceUnitId,
1092
1147
  sdkId: "108",
1093
1148
  availableStyleIds: ["1", "2"]
1094
1149
  });
@@ -1137,17 +1192,21 @@ function initialize(options) {
1137
1192
  }
1138
1193
  function attach(adGroupId, target, options = {}) {
1139
1194
  const { callbacks } = options;
1195
+ const normalizedAdGroupId = normalizeAdGroupId(adGroupId);
1140
1196
  const rejectAttached = (error) => {
1141
1197
  const normalizedError = error instanceof Error ? error : new Error(String(error));
1142
1198
  callbacks?.onAdFailedToRender?.({
1143
1199
  slotId: "",
1144
- adGroupId,
1200
+ adGroupId: normalizedAdGroupId,
1145
1201
  adMetadata: {},
1146
1202
  error: { code: 0, message: normalizedError.message }
1147
1203
  });
1148
1204
  };
1149
1205
  try {
1150
- const spaceId = adGroupId;
1206
+ const spaceId = normalizedAdGroupId;
1207
+ if (spaceId.length === 0) {
1208
+ throw createInvalidAdGroupIdError();
1209
+ }
1151
1210
  const sdk = getAdsSdk();
1152
1211
  if (!sdk) {
1153
1212
  throw new Error("[toss-ad] Call initialize() before attaching an ad.");
@@ -1164,7 +1223,7 @@ function attach(adGroupId, target, options = {}) {
1164
1223
  autoLoad: true,
1165
1224
  theme: options.theme,
1166
1225
  padding: options.padding,
1167
- callbacks: wrapCallbacks(adGroupId, options.callbacks)
1226
+ callbacks: wrapCallbacks(normalizedAdGroupId, options.callbacks)
1168
1227
  };
1169
1228
  sdk.banner.createSlot(element, slotOptions);
1170
1229
  } catch (error) {
@@ -1178,6 +1237,7 @@ var DEFAULT_ATTACH_TONE = "blackAndWhite";
1178
1237
  var DEFAULT_ATTACH_VARIANT = "expanded";
1179
1238
  var ATTACH_CLASS_NAME = "toss-ads-attach";
1180
1239
  var ATTACH_STYLE_ID = "toss-ads-attach-style";
1240
+ var ATTACH_WRAPPER_ATTRIBUTE = "data-toss-ads-attach-banner-wrapper";
1181
1241
  function ensureAttachStyles(container) {
1182
1242
  const documentRef = container.ownerDocument;
1183
1243
  if (!documentRef) {
@@ -1206,6 +1266,13 @@ function ensureAttachStyles(container) {
1206
1266
  }
1207
1267
  styleContainer.appendChild(style);
1208
1268
  }
1269
+ function removeStaleAttachedBannerWrappers(container) {
1270
+ Array.from(container.children).forEach((child) => {
1271
+ if (child instanceof HTMLElement && child.getAttribute(ATTACH_WRAPPER_ATTRIBUTE) === "true") {
1272
+ container.removeChild(child);
1273
+ }
1274
+ });
1275
+ }
1209
1276
  function attachBanner(adGroupId, target, options = {}) {
1210
1277
  const {
1211
1278
  callbacks,
@@ -1213,46 +1280,22 @@ function attachBanner(adGroupId, target, options = {}) {
1213
1280
  tone = DEFAULT_ATTACH_TONE,
1214
1281
  variant = DEFAULT_ATTACH_VARIANT
1215
1282
  } = options;
1216
- const wrapper = document.createElement("div");
1217
- wrapper.style.width = "100%";
1218
- wrapper.style.height = "100%";
1219
- wrapper.style.boxSizing = "border-box";
1220
- wrapper.style.display = "flex";
1221
- wrapper.style.flexDirection = "column";
1222
- wrapper.style.justifyContent = "center";
1223
- wrapper.style.overflow = "hidden";
1224
- if (variant === "card") {
1225
- wrapper.style.padding = "0 10px";
1226
- }
1227
- const element = document.createElement("div");
1228
- element.classList.add(ATTACH_CLASS_NAME);
1229
- if (tone === "grey") {
1230
- element.classList.add("toss-ads-tone-grey");
1231
- }
1232
- if (theme === "light") {
1233
- element.classList.add("toss-ads-theme-light");
1234
- } else if (theme === "dark") {
1235
- element.classList.add("toss-ads-theme-dark");
1236
- }
1237
- if (variant === "card") {
1238
- element.style.borderRadius = "16px";
1239
- element.style.overflow = "hidden";
1240
- }
1241
- wrapper.appendChild(element);
1242
- let isAttached = false;
1243
- let slot = null;
1283
+ const normalizedAdGroupId = normalizeAdGroupId(adGroupId);
1244
1284
  const rejectAttached = (error) => {
1245
1285
  const normalizedError = error instanceof Error ? error : new Error(String(error));
1246
1286
  callbacks?.onAdFailedToRender?.({
1247
1287
  slotId: "",
1248
- adGroupId,
1288
+ adGroupId: normalizedAdGroupId,
1249
1289
  adMetadata: {},
1250
1290
  error: { code: 0, message: normalizedError.message }
1251
1291
  });
1252
1292
  };
1253
- const wrappedCallbacks = wrapCallbacks(adGroupId, callbacks);
1293
+ const wrappedCallbacks = wrapCallbacks(normalizedAdGroupId, callbacks);
1254
1294
  try {
1255
- const spaceId = adGroupId;
1295
+ const spaceId = normalizedAdGroupId;
1296
+ if (spaceId.length === 0) {
1297
+ throw createInvalidAdGroupIdError();
1298
+ }
1256
1299
  const sdk = getAdsSdk();
1257
1300
  if (!sdk) {
1258
1301
  throw new Error("[toss-ad] Call initialize() before attaching an ad.");
@@ -1264,37 +1307,77 @@ function attachBanner(adGroupId, target, options = {}) {
1264
1307
  if (!container) {
1265
1308
  throw new Error(`[toss-ad] Failed to find target element: ${target}`);
1266
1309
  }
1267
- ensureAttachStyles(container);
1268
- container.appendChild(wrapper);
1269
- isAttached = true;
1270
- const slotOptions = {
1271
- spaceId,
1272
- autoLoad: true,
1273
- theme: theme === "auto" ? void 0 : theme,
1274
- renderPadding: (styleId) => {
1275
- if (styleId === "1") {
1276
- return DEFAULT_ATTACH_PADDING_BANNER;
1277
- } else {
1278
- return DEFAULT_ATTACH_PADDING_NATIVE_IMAGE;
1310
+ return getOrCreateAttachedBanner(container, () => {
1311
+ const wrapper = document.createElement("div");
1312
+ wrapper.style.width = "100%";
1313
+ wrapper.style.height = "100%";
1314
+ wrapper.style.boxSizing = "border-box";
1315
+ wrapper.style.display = "flex";
1316
+ wrapper.style.flexDirection = "column";
1317
+ wrapper.style.justifyContent = "center";
1318
+ wrapper.style.overflow = "hidden";
1319
+ wrapper.setAttribute(ATTACH_WRAPPER_ATTRIBUTE, "true");
1320
+ if (variant === "card") {
1321
+ wrapper.style.padding = "0 10px";
1322
+ }
1323
+ const element = document.createElement("div");
1324
+ element.classList.add(ATTACH_CLASS_NAME);
1325
+ if (tone === "grey") {
1326
+ element.classList.add("toss-ads-tone-grey");
1327
+ }
1328
+ if (theme === "light") {
1329
+ element.classList.add("toss-ads-theme-light");
1330
+ } else if (theme === "dark") {
1331
+ element.classList.add("toss-ads-theme-dark");
1332
+ }
1333
+ if (variant === "card") {
1334
+ element.style.borderRadius = "16px";
1335
+ element.style.overflow = "hidden";
1336
+ }
1337
+ wrapper.appendChild(element);
1338
+ let isAttached = false;
1339
+ let slot = null;
1340
+ try {
1341
+ ensureAttachStyles(container);
1342
+ removeStaleAttachedBannerWrappers(container);
1343
+ container.appendChild(wrapper);
1344
+ isAttached = true;
1345
+ const slotOptions = {
1346
+ spaceId,
1347
+ autoLoad: true,
1348
+ theme: theme === "auto" ? void 0 : theme,
1349
+ renderPadding: (styleId) => {
1350
+ if (styleId === "1") {
1351
+ return DEFAULT_ATTACH_PADDING_BANNER;
1352
+ }
1353
+ return DEFAULT_ATTACH_PADDING_NATIVE_IMAGE;
1354
+ },
1355
+ callbacks: wrappedCallbacks
1356
+ };
1357
+ slot = sdk.banner.createSlot(element, slotOptions);
1358
+ } catch (error) {
1359
+ if (isAttached && wrapper.parentNode) {
1360
+ wrapper.parentNode.removeChild(wrapper);
1279
1361
  }
1280
- },
1281
- callbacks: wrappedCallbacks
1282
- };
1283
- slot = sdk.banner.createSlot(element, slotOptions);
1362
+ throw error;
1363
+ }
1364
+ return {
1365
+ destroy() {
1366
+ slot?.destroy();
1367
+ if (isAttached && wrapper.parentNode) {
1368
+ wrapper.parentNode.removeChild(wrapper);
1369
+ }
1370
+ isAttached = false;
1371
+ }
1372
+ };
1373
+ });
1284
1374
  } catch (error) {
1285
- if (isAttached && wrapper.parentNode) {
1286
- wrapper.parentNode.removeChild(wrapper);
1287
- }
1288
1375
  rejectAttached(error);
1289
- }
1290
- return {
1291
- destroy() {
1292
- slot?.destroy();
1293
- if (isAttached && wrapper.parentNode) {
1294
- wrapper.parentNode.removeChild(wrapper);
1376
+ return {
1377
+ destroy() {
1295
1378
  }
1296
- }
1297
- };
1379
+ };
1380
+ }
1298
1381
  }
1299
1382
  function destroy(slotId) {
1300
1383
  const sdk = getAdsSdk();
@@ -1309,6 +1392,7 @@ function destroyAll() {
1309
1392
  return;
1310
1393
  }
1311
1394
  sdk.banner.destroyAll();
1395
+ resetAttachedBannerRegistry();
1312
1396
  }
1313
1397
  function wrapCallbacks(adGroupId, callbacks) {
1314
1398
  if (!callbacks) {
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.0",
4
+ "version": "2.4.2",
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.4.0"
30
+ "@apps-in-toss/types": "2.4.2"
31
31
  },
32
32
  "devDependencies": {
33
- "@apps-in-toss/bridge-core": "2.4.0",
34
- "@apps-in-toss/native-modules": "^2.4.0",
33
+ "@apps-in-toss/bridge-core": "2.4.2",
34
+ "@apps-in-toss/native-modules": "^2.4.2",
35
35
  "@swc/core": "^1.12.7",
36
36
  "picocolors": "^1.1.1",
37
37
  "ts-morph": "^26.0.0",