@suucha/ads-adsense 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Suucha Studio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # @suucha/ads-adsense
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](../../LICENSE)
4
+
5
+ Google AdSense ad manager implementation for Suucha Web SDK. Dynamically loads `adsbygoogle.js` and mounts ad slots into DOM containers.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pnpm add @suucha/core @suucha/ads-adsense
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ### Auto Ads (simplest)
16
+
17
+ ```typescript
18
+ import { suucha } from '@suucha/core';
19
+ import { AdSenseManager } from '@suucha/ads-adsense';
20
+
21
+ const ads = new AdSenseManager({
22
+ client: 'ca-pub-1234567890123456',
23
+ autoAds: true,
24
+ });
25
+
26
+ suucha.enableAds(ads);
27
+ // AdSense automatically picks ad placements
28
+ ```
29
+
30
+ ### Manual Slots
31
+
32
+ ```typescript
33
+ const ads = new AdSenseManager({
34
+ client: 'ca-pub-1234567890123456',
35
+ });
36
+
37
+ suucha.enableAds(ads);
38
+
39
+ const handle = await ads.mountSlot({
40
+ container: '#ad-banner', // CSS selector or HTMLElement
41
+ slot: '1234567890',
42
+ format: 'horizontal',
43
+ });
44
+
45
+ // Refresh slot
46
+ handle.refresh();
47
+
48
+ // Tear down
49
+ ads.unmountSlot(handle);
50
+ ```
51
+
52
+ ## Consent / GDPR
53
+
54
+ ```typescript
55
+ const ads = new AdSenseManager({
56
+ client: 'ca-pub-...',
57
+ deferUntilConsent: true,
58
+ });
59
+
60
+ // Script not loaded yet
61
+ suucha.enableAds(ads);
62
+
63
+ // After CMP grants consent:
64
+ suucha.setConsent(true);
65
+ ads.load();
66
+ ```
67
+
68
+ ## Options
69
+
70
+ | Option | Default | Description |
71
+ |---|---|---|
72
+ | `client` | (required) | Publisher ID (`ca-pub-XXXX`) |
73
+ | `autoAds` | `false` | Enable AdSense auto-ad placement |
74
+ | `deferUntilConsent` | `false` | Skip script load until `load()` called |
75
+
76
+ ## Slot Options
77
+
78
+ | Option | Default | Description |
79
+ |---|---|---|
80
+ | `container` | (required) | CSS selector or HTMLElement |
81
+ | `slot` | - | Ad unit ID |
82
+ | `format` | `'auto'` | `'auto'` / `'horizontal'` / `'vertical'` / `'rectangle'` |
83
+ | `responsive` | `true` | Fill container width |
84
+
85
+ ## License
86
+
87
+ MIT © Suucha Studio
package/dist/index.cjs ADDED
@@ -0,0 +1,119 @@
1
+ 'use strict';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
6
+
7
+ // src/index.ts
8
+ var DEFAULT_SCRIPT_SRC = "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js";
9
+ var nextSlotId = 1;
10
+ var AdSenseManager = class {
11
+ constructor(options) {
12
+ __publicField(this, "options");
13
+ __publicField(this, "loaded", false);
14
+ __publicField(this, "slots", /* @__PURE__ */ new Map());
15
+ if (!options.client || !options.client.startsWith("ca-pub-")) {
16
+ throw new Error(
17
+ `[AdSenseManager] client must start with 'ca-pub-', got: ${options.client}`
18
+ );
19
+ }
20
+ this.options = {
21
+ autoAds: false,
22
+ deferUntilConsent: false,
23
+ scriptSrc: DEFAULT_SCRIPT_SRC,
24
+ ...options
25
+ };
26
+ if (!this.options.deferUntilConsent) {
27
+ this.load();
28
+ }
29
+ if (this.options.autoAds) {
30
+ this.enableAutoAds();
31
+ }
32
+ }
33
+ /**
34
+ * 加载 AdSense 脚本
35
+ */
36
+ load() {
37
+ if (this.loaded) return;
38
+ if (typeof document === "undefined") return;
39
+ const script = document.createElement("script");
40
+ script.async = true;
41
+ script.src = `${this.options.scriptSrc}?client=${this.options.client}`;
42
+ script.crossOrigin = "anonymous";
43
+ document.head.appendChild(script);
44
+ window.adsbygoogle = window.adsbygoogle || [];
45
+ this.loaded = true;
46
+ }
47
+ /**
48
+ * 启用自动广告(AdSense 自动选择位置)
49
+ */
50
+ enableAutoAds() {
51
+ if (typeof window === "undefined" || !window.adsbygoogle) return;
52
+ window.adsbygoogle.push({ google_ad_client: this.options.client });
53
+ }
54
+ /**
55
+ * 挂载广告位到 DOM
56
+ */
57
+ async mountSlot(slotOptions) {
58
+ if (typeof document === "undefined") {
59
+ throw new Error("[AdSenseManager] mountSlot requires a browser environment");
60
+ }
61
+ const container = typeof slotOptions.container === "string" ? document.querySelector(slotOptions.container) : slotOptions.container;
62
+ if (!container) {
63
+ throw new Error(
64
+ `[AdSenseManager] Container not found: ${slotOptions.container}`
65
+ );
66
+ }
67
+ const ins = document.createElement("ins");
68
+ ins.className = "adsbygoogle";
69
+ ins.style.display = "block";
70
+ ins.setAttribute("data-ad-client", this.options.client);
71
+ if (slotOptions.slot) {
72
+ ins.setAttribute("data-ad-slot", slotOptions.slot);
73
+ }
74
+ ins.setAttribute(
75
+ "data-ad-format",
76
+ slotOptions.format ?? "auto"
77
+ );
78
+ if (slotOptions.responsive !== false) {
79
+ ins.setAttribute("data-full-width-responsive", "true");
80
+ }
81
+ container.appendChild(ins);
82
+ if (window.adsbygoogle) {
83
+ window.adsbygoogle.push({});
84
+ }
85
+ const id = `adsense_slot_${nextSlotId++}`;
86
+ const handle = {
87
+ id,
88
+ slot: slotOptions.slot ?? "",
89
+ refresh: () => {
90
+ if (window.adsbygoogle) {
91
+ window.adsbygoogle.push({ refresh: () => {
92
+ } });
93
+ }
94
+ },
95
+ destroy: () => {
96
+ ins.remove();
97
+ this.slots.delete(id);
98
+ }
99
+ };
100
+ this.slots.set(id, handle);
101
+ return handle;
102
+ }
103
+ /**
104
+ * 卸载广告位
105
+ */
106
+ unmountSlot(handle) {
107
+ handle.destroy();
108
+ }
109
+ /**
110
+ * 刷新广告位(重新竞价)
111
+ */
112
+ refresh(handle) {
113
+ handle.refresh();
114
+ }
115
+ };
116
+
117
+ exports.AdSenseManager = AdSenseManager;
118
+ //# sourceMappingURL=index.cjs.map
119
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\r\n * @suucha/ads-adsense\r\n *\r\n * Google AdSense 广告管理实现。\r\n * 动态加载 pagead2 脚本,提供 mountSlot / unmountSlot / refresh / enableAutoAds。\r\n */\r\n\r\nimport type { AdSlotHandle, AdSlotOptions, IAdManager } from '@suucha/core';\r\n\r\nexport interface AdSenseOptions {\r\n /** 发布者 ID,必填,格式 'ca-pub-xxxxxxxxxxxxxxxx' */\r\n client: string;\r\n /** 是否启用自动广告,默认 false */\r\n autoAds?: boolean;\r\n /** 是否在 consent 未授予时不加载脚本 */\r\n deferUntilConsent?: boolean;\r\n /** 自定义脚本来源(用于测试或代理) */\r\n scriptSrc?: string;\r\n}\r\n\r\nexport interface AdSenseSlotOptions extends AdSlotOptions {\r\n /** 广告单元 ID(如 '1234567890') */\r\n slot?: string;\r\n /** 广告格式,默认 'auto' */\r\n format?: 'auto' | 'horizontal' | 'vertical' | 'rectangle';\r\n /** 是否响应式,默认 true */\r\n responsive?: boolean;\r\n}\r\n\r\ninterface AdSenseSlotHandleInternal extends AdSlotHandle {\r\n refresh(): void;\r\n}\r\n\r\ndeclare global {\r\n interface Window {\r\n adsbygoogle?: unknown[];\r\n }\r\n}\r\n\r\nconst DEFAULT_SCRIPT_SRC = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js';\r\n\r\nlet nextSlotId = 1;\r\n\r\nexport class AdSenseManager implements IAdManager {\r\n private readonly options: Required<AdSenseOptions>;\r\n private loaded = false;\r\n private readonly slots = new Map<string, AdSenseSlotHandleInternal>();\r\n\r\n constructor(options: AdSenseOptions) {\r\n if (!options.client || !options.client.startsWith('ca-pub-')) {\r\n throw new Error(\r\n `[AdSenseManager] client must start with 'ca-pub-', got: ${options.client}`,\r\n );\r\n }\r\n this.options = {\r\n autoAds: false,\r\n deferUntilConsent: false,\r\n scriptSrc: DEFAULT_SCRIPT_SRC,\r\n ...options,\r\n };\r\n\r\n if (!this.options.deferUntilConsent) {\r\n this.load();\r\n }\r\n\r\n if (this.options.autoAds) {\r\n this.enableAutoAds();\r\n }\r\n }\r\n\r\n /**\r\n * 加载 AdSense 脚本\r\n */\r\n load(): void {\r\n if (this.loaded) return;\r\n if (typeof document === 'undefined') return;\r\n\r\n const script = document.createElement('script');\r\n script.async = true;\r\n script.src = `${this.options.scriptSrc}?client=${this.options.client}`;\r\n script.crossOrigin = 'anonymous';\r\n document.head.appendChild(script);\r\n\r\n window.adsbygoogle = window.adsbygoogle || [];\r\n this.loaded = true;\r\n }\r\n\r\n /**\r\n * 启用自动广告(AdSense 自动选择位置)\r\n */\r\n enableAutoAds(): void {\r\n if (typeof window === 'undefined' || !window.adsbygoogle) return;\r\n window.adsbygoogle.push({ google_ad_client: this.options.client });\r\n }\r\n\r\n /**\r\n * 挂载广告位到 DOM\r\n */\r\n async mountSlot(slotOptions: AdSenseSlotOptions): Promise<AdSenseSlotHandleInternal> {\r\n if (typeof document === 'undefined') {\r\n throw new Error('[AdSenseManager] mountSlot requires a browser environment');\r\n }\r\n const container =\r\n typeof slotOptions.container === 'string'\r\n ? document.querySelector<HTMLElement>(slotOptions.container)\r\n : slotOptions.container;\r\n if (!container) {\r\n throw new Error(\r\n `[AdSenseManager] Container not found: ${slotOptions.container}`,\r\n );\r\n }\r\n\r\n const ins = document.createElement('ins');\r\n ins.className = 'adsbygoogle';\r\n ins.style.display = 'block';\r\n ins.setAttribute('data-ad-client', this.options.client);\r\n if (slotOptions.slot) {\r\n ins.setAttribute('data-ad-slot', slotOptions.slot);\r\n }\r\n ins.setAttribute(\r\n 'data-ad-format',\r\n slotOptions.format ?? 'auto',\r\n );\r\n if (slotOptions.responsive !== false) {\r\n ins.setAttribute('data-full-width-responsive', 'true');\r\n }\r\n container.appendChild(ins);\r\n\r\n if (window.adsbygoogle) {\r\n window.adsbygoogle.push({});\r\n }\r\n\r\n const id = `adsense_slot_${nextSlotId++}`;\r\n const handle: AdSenseSlotHandleInternal = {\r\n id,\r\n slot: slotOptions.slot ?? '',\r\n refresh: () => {\r\n if (window.adsbygoogle) {\r\n window.adsbygoogle.push({ refresh: () => {} });\r\n }\r\n },\r\n destroy: () => {\r\n ins.remove();\r\n this.slots.delete(id);\r\n },\r\n };\r\n this.slots.set(id, handle);\r\n return handle;\r\n }\r\n\r\n /**\r\n * 卸载广告位\r\n */\r\n unmountSlot(handle: AdSenseSlotHandleInternal): void {\r\n handle.destroy();\r\n }\r\n\r\n /**\r\n * 刷新广告位(重新竞价)\r\n */\r\n refresh(handle: AdSenseSlotHandleInternal): void {\r\n handle.refresh();\r\n }\r\n}\r\n\r\nexport type { AdSenseSlotHandleInternal };\r\n"]}
@@ -0,0 +1,63 @@
1
+ import { IAdManager, AdSlotOptions, AdSlotHandle } from '@suucha/core';
2
+
3
+ /**
4
+ * @suucha/ads-adsense
5
+ *
6
+ * Google AdSense 广告管理实现。
7
+ * 动态加载 pagead2 脚本,提供 mountSlot / unmountSlot / refresh / enableAutoAds。
8
+ */
9
+
10
+ interface AdSenseOptions {
11
+ /** 发布者 ID,必填,格式 'ca-pub-xxxxxxxxxxxxxxxx' */
12
+ client: string;
13
+ /** 是否启用自动广告,默认 false */
14
+ autoAds?: boolean;
15
+ /** 是否在 consent 未授予时不加载脚本 */
16
+ deferUntilConsent?: boolean;
17
+ /** 自定义脚本来源(用于测试或代理) */
18
+ scriptSrc?: string;
19
+ }
20
+ interface AdSenseSlotOptions extends AdSlotOptions {
21
+ /** 广告单元 ID(如 '1234567890') */
22
+ slot?: string;
23
+ /** 广告格式,默认 'auto' */
24
+ format?: 'auto' | 'horizontal' | 'vertical' | 'rectangle';
25
+ /** 是否响应式,默认 true */
26
+ responsive?: boolean;
27
+ }
28
+ interface AdSenseSlotHandleInternal extends AdSlotHandle {
29
+ refresh(): void;
30
+ }
31
+ declare global {
32
+ interface Window {
33
+ adsbygoogle?: unknown[];
34
+ }
35
+ }
36
+ declare class AdSenseManager implements IAdManager {
37
+ private readonly options;
38
+ private loaded;
39
+ private readonly slots;
40
+ constructor(options: AdSenseOptions);
41
+ /**
42
+ * 加载 AdSense 脚本
43
+ */
44
+ load(): void;
45
+ /**
46
+ * 启用自动广告(AdSense 自动选择位置)
47
+ */
48
+ enableAutoAds(): void;
49
+ /**
50
+ * 挂载广告位到 DOM
51
+ */
52
+ mountSlot(slotOptions: AdSenseSlotOptions): Promise<AdSenseSlotHandleInternal>;
53
+ /**
54
+ * 卸载广告位
55
+ */
56
+ unmountSlot(handle: AdSenseSlotHandleInternal): void;
57
+ /**
58
+ * 刷新广告位(重新竞价)
59
+ */
60
+ refresh(handle: AdSenseSlotHandleInternal): void;
61
+ }
62
+
63
+ export { AdSenseManager, type AdSenseOptions, type AdSenseSlotHandleInternal, type AdSenseSlotOptions };
@@ -0,0 +1,63 @@
1
+ import { IAdManager, AdSlotOptions, AdSlotHandle } from '@suucha/core';
2
+
3
+ /**
4
+ * @suucha/ads-adsense
5
+ *
6
+ * Google AdSense 广告管理实现。
7
+ * 动态加载 pagead2 脚本,提供 mountSlot / unmountSlot / refresh / enableAutoAds。
8
+ */
9
+
10
+ interface AdSenseOptions {
11
+ /** 发布者 ID,必填,格式 'ca-pub-xxxxxxxxxxxxxxxx' */
12
+ client: string;
13
+ /** 是否启用自动广告,默认 false */
14
+ autoAds?: boolean;
15
+ /** 是否在 consent 未授予时不加载脚本 */
16
+ deferUntilConsent?: boolean;
17
+ /** 自定义脚本来源(用于测试或代理) */
18
+ scriptSrc?: string;
19
+ }
20
+ interface AdSenseSlotOptions extends AdSlotOptions {
21
+ /** 广告单元 ID(如 '1234567890') */
22
+ slot?: string;
23
+ /** 广告格式,默认 'auto' */
24
+ format?: 'auto' | 'horizontal' | 'vertical' | 'rectangle';
25
+ /** 是否响应式,默认 true */
26
+ responsive?: boolean;
27
+ }
28
+ interface AdSenseSlotHandleInternal extends AdSlotHandle {
29
+ refresh(): void;
30
+ }
31
+ declare global {
32
+ interface Window {
33
+ adsbygoogle?: unknown[];
34
+ }
35
+ }
36
+ declare class AdSenseManager implements IAdManager {
37
+ private readonly options;
38
+ private loaded;
39
+ private readonly slots;
40
+ constructor(options: AdSenseOptions);
41
+ /**
42
+ * 加载 AdSense 脚本
43
+ */
44
+ load(): void;
45
+ /**
46
+ * 启用自动广告(AdSense 自动选择位置)
47
+ */
48
+ enableAutoAds(): void;
49
+ /**
50
+ * 挂载广告位到 DOM
51
+ */
52
+ mountSlot(slotOptions: AdSenseSlotOptions): Promise<AdSenseSlotHandleInternal>;
53
+ /**
54
+ * 卸载广告位
55
+ */
56
+ unmountSlot(handle: AdSenseSlotHandleInternal): void;
57
+ /**
58
+ * 刷新广告位(重新竞价)
59
+ */
60
+ refresh(handle: AdSenseSlotHandleInternal): void;
61
+ }
62
+
63
+ export { AdSenseManager, type AdSenseOptions, type AdSenseSlotHandleInternal, type AdSenseSlotOptions };
package/dist/index.js ADDED
@@ -0,0 +1,117 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+
5
+ // src/index.ts
6
+ var DEFAULT_SCRIPT_SRC = "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js";
7
+ var nextSlotId = 1;
8
+ var AdSenseManager = class {
9
+ constructor(options) {
10
+ __publicField(this, "options");
11
+ __publicField(this, "loaded", false);
12
+ __publicField(this, "slots", /* @__PURE__ */ new Map());
13
+ if (!options.client || !options.client.startsWith("ca-pub-")) {
14
+ throw new Error(
15
+ `[AdSenseManager] client must start with 'ca-pub-', got: ${options.client}`
16
+ );
17
+ }
18
+ this.options = {
19
+ autoAds: false,
20
+ deferUntilConsent: false,
21
+ scriptSrc: DEFAULT_SCRIPT_SRC,
22
+ ...options
23
+ };
24
+ if (!this.options.deferUntilConsent) {
25
+ this.load();
26
+ }
27
+ if (this.options.autoAds) {
28
+ this.enableAutoAds();
29
+ }
30
+ }
31
+ /**
32
+ * 加载 AdSense 脚本
33
+ */
34
+ load() {
35
+ if (this.loaded) return;
36
+ if (typeof document === "undefined") return;
37
+ const script = document.createElement("script");
38
+ script.async = true;
39
+ script.src = `${this.options.scriptSrc}?client=${this.options.client}`;
40
+ script.crossOrigin = "anonymous";
41
+ document.head.appendChild(script);
42
+ window.adsbygoogle = window.adsbygoogle || [];
43
+ this.loaded = true;
44
+ }
45
+ /**
46
+ * 启用自动广告(AdSense 自动选择位置)
47
+ */
48
+ enableAutoAds() {
49
+ if (typeof window === "undefined" || !window.adsbygoogle) return;
50
+ window.adsbygoogle.push({ google_ad_client: this.options.client });
51
+ }
52
+ /**
53
+ * 挂载广告位到 DOM
54
+ */
55
+ async mountSlot(slotOptions) {
56
+ if (typeof document === "undefined") {
57
+ throw new Error("[AdSenseManager] mountSlot requires a browser environment");
58
+ }
59
+ const container = typeof slotOptions.container === "string" ? document.querySelector(slotOptions.container) : slotOptions.container;
60
+ if (!container) {
61
+ throw new Error(
62
+ `[AdSenseManager] Container not found: ${slotOptions.container}`
63
+ );
64
+ }
65
+ const ins = document.createElement("ins");
66
+ ins.className = "adsbygoogle";
67
+ ins.style.display = "block";
68
+ ins.setAttribute("data-ad-client", this.options.client);
69
+ if (slotOptions.slot) {
70
+ ins.setAttribute("data-ad-slot", slotOptions.slot);
71
+ }
72
+ ins.setAttribute(
73
+ "data-ad-format",
74
+ slotOptions.format ?? "auto"
75
+ );
76
+ if (slotOptions.responsive !== false) {
77
+ ins.setAttribute("data-full-width-responsive", "true");
78
+ }
79
+ container.appendChild(ins);
80
+ if (window.adsbygoogle) {
81
+ window.adsbygoogle.push({});
82
+ }
83
+ const id = `adsense_slot_${nextSlotId++}`;
84
+ const handle = {
85
+ id,
86
+ slot: slotOptions.slot ?? "",
87
+ refresh: () => {
88
+ if (window.adsbygoogle) {
89
+ window.adsbygoogle.push({ refresh: () => {
90
+ } });
91
+ }
92
+ },
93
+ destroy: () => {
94
+ ins.remove();
95
+ this.slots.delete(id);
96
+ }
97
+ };
98
+ this.slots.set(id, handle);
99
+ return handle;
100
+ }
101
+ /**
102
+ * 卸载广告位
103
+ */
104
+ unmountSlot(handle) {
105
+ handle.destroy();
106
+ }
107
+ /**
108
+ * 刷新广告位(重新竞价)
109
+ */
110
+ refresh(handle) {
111
+ handle.refresh();
112
+ }
113
+ };
114
+
115
+ export { AdSenseManager };
116
+ //# sourceMappingURL=index.js.map
117
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * @suucha/ads-adsense\r\n *\r\n * Google AdSense 广告管理实现。\r\n * 动态加载 pagead2 脚本,提供 mountSlot / unmountSlot / refresh / enableAutoAds。\r\n */\r\n\r\nimport type { AdSlotHandle, AdSlotOptions, IAdManager } from '@suucha/core';\r\n\r\nexport interface AdSenseOptions {\r\n /** 发布者 ID,必填,格式 'ca-pub-xxxxxxxxxxxxxxxx' */\r\n client: string;\r\n /** 是否启用自动广告,默认 false */\r\n autoAds?: boolean;\r\n /** 是否在 consent 未授予时不加载脚本 */\r\n deferUntilConsent?: boolean;\r\n /** 自定义脚本来源(用于测试或代理) */\r\n scriptSrc?: string;\r\n}\r\n\r\nexport interface AdSenseSlotOptions extends AdSlotOptions {\r\n /** 广告单元 ID(如 '1234567890') */\r\n slot?: string;\r\n /** 广告格式,默认 'auto' */\r\n format?: 'auto' | 'horizontal' | 'vertical' | 'rectangle';\r\n /** 是否响应式,默认 true */\r\n responsive?: boolean;\r\n}\r\n\r\ninterface AdSenseSlotHandleInternal extends AdSlotHandle {\r\n refresh(): void;\r\n}\r\n\r\ndeclare global {\r\n interface Window {\r\n adsbygoogle?: unknown[];\r\n }\r\n}\r\n\r\nconst DEFAULT_SCRIPT_SRC = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js';\r\n\r\nlet nextSlotId = 1;\r\n\r\nexport class AdSenseManager implements IAdManager {\r\n private readonly options: Required<AdSenseOptions>;\r\n private loaded = false;\r\n private readonly slots = new Map<string, AdSenseSlotHandleInternal>();\r\n\r\n constructor(options: AdSenseOptions) {\r\n if (!options.client || !options.client.startsWith('ca-pub-')) {\r\n throw new Error(\r\n `[AdSenseManager] client must start with 'ca-pub-', got: ${options.client}`,\r\n );\r\n }\r\n this.options = {\r\n autoAds: false,\r\n deferUntilConsent: false,\r\n scriptSrc: DEFAULT_SCRIPT_SRC,\r\n ...options,\r\n };\r\n\r\n if (!this.options.deferUntilConsent) {\r\n this.load();\r\n }\r\n\r\n if (this.options.autoAds) {\r\n this.enableAutoAds();\r\n }\r\n }\r\n\r\n /**\r\n * 加载 AdSense 脚本\r\n */\r\n load(): void {\r\n if (this.loaded) return;\r\n if (typeof document === 'undefined') return;\r\n\r\n const script = document.createElement('script');\r\n script.async = true;\r\n script.src = `${this.options.scriptSrc}?client=${this.options.client}`;\r\n script.crossOrigin = 'anonymous';\r\n document.head.appendChild(script);\r\n\r\n window.adsbygoogle = window.adsbygoogle || [];\r\n this.loaded = true;\r\n }\r\n\r\n /**\r\n * 启用自动广告(AdSense 自动选择位置)\r\n */\r\n enableAutoAds(): void {\r\n if (typeof window === 'undefined' || !window.adsbygoogle) return;\r\n window.adsbygoogle.push({ google_ad_client: this.options.client });\r\n }\r\n\r\n /**\r\n * 挂载广告位到 DOM\r\n */\r\n async mountSlot(slotOptions: AdSenseSlotOptions): Promise<AdSenseSlotHandleInternal> {\r\n if (typeof document === 'undefined') {\r\n throw new Error('[AdSenseManager] mountSlot requires a browser environment');\r\n }\r\n const container =\r\n typeof slotOptions.container === 'string'\r\n ? document.querySelector<HTMLElement>(slotOptions.container)\r\n : slotOptions.container;\r\n if (!container) {\r\n throw new Error(\r\n `[AdSenseManager] Container not found: ${slotOptions.container}`,\r\n );\r\n }\r\n\r\n const ins = document.createElement('ins');\r\n ins.className = 'adsbygoogle';\r\n ins.style.display = 'block';\r\n ins.setAttribute('data-ad-client', this.options.client);\r\n if (slotOptions.slot) {\r\n ins.setAttribute('data-ad-slot', slotOptions.slot);\r\n }\r\n ins.setAttribute(\r\n 'data-ad-format',\r\n slotOptions.format ?? 'auto',\r\n );\r\n if (slotOptions.responsive !== false) {\r\n ins.setAttribute('data-full-width-responsive', 'true');\r\n }\r\n container.appendChild(ins);\r\n\r\n if (window.adsbygoogle) {\r\n window.adsbygoogle.push({});\r\n }\r\n\r\n const id = `adsense_slot_${nextSlotId++}`;\r\n const handle: AdSenseSlotHandleInternal = {\r\n id,\r\n slot: slotOptions.slot ?? '',\r\n refresh: () => {\r\n if (window.adsbygoogle) {\r\n window.adsbygoogle.push({ refresh: () => {} });\r\n }\r\n },\r\n destroy: () => {\r\n ins.remove();\r\n this.slots.delete(id);\r\n },\r\n };\r\n this.slots.set(id, handle);\r\n return handle;\r\n }\r\n\r\n /**\r\n * 卸载广告位\r\n */\r\n unmountSlot(handle: AdSenseSlotHandleInternal): void {\r\n handle.destroy();\r\n }\r\n\r\n /**\r\n * 刷新广告位(重新竞价)\r\n */\r\n refresh(handle: AdSenseSlotHandleInternal): void {\r\n handle.refresh();\r\n }\r\n}\r\n\r\nexport type { AdSenseSlotHandleInternal };\r\n"]}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@suucha/ads-adsense",
3
+ "version": "0.1.0",
4
+ "description": "Google AdSense ad manager for Suucha Web SDK",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "keywords": [
21
+ "suucha",
22
+ "ads",
23
+ "adsense",
24
+ "google"
25
+ ],
26
+ "license": "MIT",
27
+ "author": "Suucha Studio",
28
+ "homepage": "https://github.com/suucha/Suucha.Web.Core#readme",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/suucha/Suucha.Web.Core.git",
32
+ "directory": "packages/ads-adsense"
33
+ },
34
+ "bugs": {
35
+ "url": "https://github.com/suucha/Suucha.Web.Core/issues"
36
+ },
37
+ "dependencies": {
38
+ "@suucha/core": "0.1.0"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "scripts": {
44
+ "build": "tsup",
45
+ "dev": "tsup --watch",
46
+ "typecheck": "tsc --noEmit"
47
+ }
48
+ }