@cunpingtai/webad-react-ad-scripts 0.1.0 → 0.2.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @cunpingtai/webad-react-ad-scripts
2
2
 
3
- 面向 Next.js 和 TanStack React 项目的广告组件包。`AdsProvider` 从 WebAd Cloudflare Worker 读取当前站点策略;各广告组件根据策略决定是否渲染,并对脚本 URL 和参数做失败关闭校验。
3
+ 给 Next.js 和 TanStack React 项目使用的中央广告组件包。网站只写广告位名称,具体 Adsterra 参数和显示策略由 WebAd 后台按当前 hostname 下发。
4
4
 
5
5
  ## 安装
6
6
 
@@ -10,252 +10,80 @@ pnpm add @cunpingtai/webad-react-ad-scripts
10
10
 
11
11
  需要 React 18.3 或 React 19。
12
12
 
13
- ## Next.js App Router
13
+ ## 根部接入
14
14
 
15
- 新建一个客户端 Provider。`pageKey` 在每次真实路由切换时变化,因此同一广告位可以在新页面重新初始化;普通 state 更新不会重新加载广告。
15
+ Next.js App Router:
16
16
 
17
17
  ```tsx
18
- // app/advertising-provider.tsx
19
18
  "use client";
20
19
 
21
20
  import { AdsProvider } from "@cunpingtai/webad-react-ad-scripts";
22
21
  import { usePathname } from "next/navigation";
23
- import type { ReactNode } from "react";
24
22
 
25
- export function AdvertisingProvider({ children }: { children: ReactNode }) {
23
+ export function AdvertisingProvider({ children }: React.PropsWithChildren) {
26
24
  const pathname = usePathname();
27
-
28
25
  return (
29
- <AdsProvider
30
- endpoint="https://webad-control-plane.example.workers.dev"
31
- pageKey={pathname}
32
- >
26
+ <AdsProvider endpoint="https://a1.yumeref.com" pageKey={pathname}>
33
27
  {children}
34
28
  </AdsProvider>
35
29
  );
36
30
  }
37
31
  ```
38
32
 
39
- 在根布局中挂载一次:
40
-
41
- ```tsx
42
- // app/layout.tsx
43
- import { AdvertisingProvider } from "./advertising-provider";
44
-
45
- export default function RootLayout({
46
- children,
47
- }: {
48
- children: React.ReactNode;
49
- }) {
50
- return (
51
- <html lang="zh-CN">
52
- <body>
53
- <AdvertisingProvider>{children}</AdvertisingProvider>
54
- </body>
55
- </html>
56
- );
57
- }
58
- ```
59
-
60
- ## TanStack Router / Start
33
+ 把 `AdvertisingProvider` 放在根 layout 中一次。TanStack Router 同理,在根路由读取 `state.location.pathname` 作为 `pageKey`。
61
34
 
62
- 在根路由组件中读取当前 pathname:
35
+ `pageKey` 只在真实路由切换时变化。不要传随机数、时间戳、Tab 状态或窗口宽度。
63
36
 
64
- ```tsx
65
- import {
66
- HeadContent,
67
- Outlet,
68
- Scripts,
69
- createRootRoute,
70
- useRouterState,
71
- } from "@tanstack/react-router";
72
- import { AdsProvider } from "@cunpingtai/webad-react-ad-scripts";
37
+ ## 后台配置
73
38
 
74
- export const Route = createRootRoute({ component: RootComponent });
39
+ 打开 `https://a1.yumeref.com/admin`,编辑对应站点:
75
40
 
76
- function RootComponent() {
77
- const pathname = useRouterState({
78
- select: (state) => state.location.pathname,
79
- });
41
+ - Social Bar、Popunder、Smartlink 每站各配置一个;
42
+ - Banner 和 Native Banner 可以添加多个;
43
+ - 每个页面广告位使用唯一名称,例如 `home-top`、`article-middle`、`sidebar-sticky`;
44
+ - Banner 填脚本地址、广告 Key、宽度和高度;
45
+ - Native Banner 填脚本地址和 Container ID。
80
46
 
81
- return (
82
- <html lang="zh-CN">
83
- <head>
84
- <HeadContent />
85
- </head>
86
- <body>
87
- <AdsProvider
88
- endpoint="https://webad-control-plane.example.workers.dev"
89
- pageKey={pathname}
90
- >
91
- <Outlet />
92
- </AdsProvider>
93
- <Scripts />
94
- </body>
95
- </html>
96
- );
97
- }
98
- ```
47
+ 包会用浏览器当前 hostname 自动获取该站点配置,不需要额外传网站标识。
99
48
 
100
- 不要给 `pageKey` 传 `Date.now()` 或随机数。它应当只在真实 SPA 路由切换时变化。
101
-
102
- ## 广告组件
103
-
104
- Adsterra 提供的 zone key、脚本 URL 和容器 ID 由使用方传入。不要传整段 HTML。
105
-
106
- ### Social Bar
49
+ ## 组件
107
50
 
108
51
  ```tsx
109
- import { AdsterraSocialBar } from "@cunpingtai/webad-react-ad-scripts";
110
-
111
- <AdsterraSocialBar
112
- slotId="global-social-bar"
113
- scriptSrc="https://social.ads-cdn.example/social.js"
114
- />;
115
- ```
116
-
117
- Social Bar 在一次完整页面加载中最多初始化一次,仅在 `adsterra-full` 模式显示。
118
-
119
- ### Native Banner
120
-
121
- ```tsx
122
- import { AdsterraNativeBanner } from "@cunpingtai/webad-react-ad-scripts";
123
-
124
- <AdsterraNativeBanner
125
- slotId="article-native-1"
126
- scriptSrc="https://native.ads-cdn.example/widget/invoke.js"
127
- containerId="container-native-example"
128
- className="my-native-ad"
129
- />;
130
- ```
131
-
132
- `containerId` 必须与该 Native Banner 代码对应,并且在当前文档中唯一。
133
-
134
- ### Banner 320×50、300×250、728×90
135
-
136
- 同一个组件支持不同固定尺寸:
137
-
138
- ```tsx
139
- import { AdsterraBanner } from "@cunpingtai/webad-react-ad-scripts";
140
-
141
- <AdsterraBanner
142
- slotId="mobile-top"
143
- scriptSrc="https://banner.ads-cdn.example/mobile/invoke.js"
144
- zoneKey="exampleMobileZone"
145
- width={320}
146
- height={50}
147
- />
148
-
149
- <AdsterraBanner
150
- slotId="content-box"
151
- scriptSrc="https://banner.ads-cdn.example/box/invoke.js"
152
- zoneKey="exampleBoxZone"
153
- width={300}
154
- height={250}
155
- params={{ placement: "article" }}
156
- />
157
-
158
- <AdsterraBanner
159
- slotId="desktop-top"
160
- scriptSrc="https://banner.ads-cdn.example/wide/invoke.js"
161
- zoneKey="exampleWideZone"
162
- width={728}
163
- height={90}
164
- />;
165
- ```
166
-
167
- 组件按 Adsterra 原始代码的方式设置 `window.atOptions` 并加载 `invoke.js`,不会自行增加外层 iframe。多个 Banner 会串行初始化,避免互相覆盖 `atOptions`。
168
-
169
- ### Smartlink
170
-
171
- ```tsx
172
- import { AdsterraSmartlink } from "@cunpingtai/webad-react-ad-scripts";
173
-
174
- <AdsterraSmartlink
175
- href="https://links.example/go?key=example"
176
- className="download-button"
177
- >
178
- Continue
179
- </AdsterraSmartlink>;
180
- ```
181
-
182
- Smartlink 是普通 `<a>`,默认新窗口打开,并固定带有 `noopener noreferrer sponsored`。
183
-
184
- ### Popunder
185
-
186
- ```tsx
187
- import { AdsterraPopunder } from "@cunpingtai/webad-react-ad-scripts";
188
-
189
- <AdsterraPopunder
190
- slotId="global-popunder"
191
- scriptSrc="https://pop.ads-cdn.example/pop.js"
192
- />;
193
- ```
194
-
195
- Popunder 在一次完整页面加载中最多初始化一次,仅在 `adsterra-full` 模式显示。
196
-
197
- ## 策略与显示规则
198
-
199
- | Worker 返回模式 | Native Banner | Banner | Smartlink | Social Bar | Popunder |
200
- | ----------------- | ------------- | ------ | --------- | ---------- | -------- |
201
- | `none` | 否 | 否 | 否 | 否 | 否 |
202
- | `adsense` | 否 | 否 | 否 | 否 | 否 |
203
- | `adsense-limited` | 是 | 是 | 否 | 否 | 否 |
204
- | `adsterra-safe` | 是 | 是 | 是 | 否 | 否 |
205
- | `adsterra-full` | 是 | 是 | 是 | 是 | 是 |
206
-
207
- AdSense Auto Ads 等全局脚本仍由 Worker 配置中的 `providers.*.scripts` 加载。Adsterra 广告位脚本由上述组件加载,因此使用组件方案时通常把 Worker 的 `adsterra.safeScripts` 和 `adsterra.fullScripts` 留空,避免重复加载。
208
-
209
- ## 生命周期与去重
210
-
211
- - 同一个 `pageKey` 中,相同 `slotId` 只初始化一次。
212
- - React 普通 re-render、Strict Mode effect 重放和同页重新挂载不会产生第二次广告请求。
213
- - `pageKey` 改变后,Native Banner 和 Banner 可以按新的 page view 重新初始化。
214
- - Social Bar 和 Popunder 无法可靠卸载,因此一次完整文档生命周期只初始化一次;SPA 路由切换不会重复加载。
215
- - 不要使用随机 React `key`,也不要因为 resize、Tab、FAQ 展开或游戏状态变化而改变 `pageKey`。
216
-
217
- ## AdsProvider 参数与状态
218
-
219
- ```ts
220
- type AdsProviderProps = {
221
- endpoint: string;
222
- pageKey: string;
223
- timeoutMs?: number; // 默认 3000
224
- nonce?: string;
225
- onStatusChange?: (status: AdsProviderStatus) => void;
226
- children: React.ReactNode;
227
- };
228
- ```
229
-
230
- 状态包括 `loading-config`、`disabled`、`loading-scripts`、`ready` 和 `error`。例如:
231
-
232
- ```tsx
233
- <AdsProvider
234
- endpoint="https://webad-control-plane.example.workers.dev"
235
- pageKey={pathname}
236
- nonce={cspNonce}
237
- onStatusChange={(status) => {
238
- if (status.state === "error") {
239
- console.error("Advertising unavailable", status.reason);
240
- }
241
- }}
242
- >
243
- {children}
244
- </AdsProvider>
52
+ import {
53
+ AdsterraBanner,
54
+ AdsterraNativeBanner,
55
+ AdsterraPopunder,
56
+ AdsterraSmartlink,
57
+ AdsterraSocialBar,
58
+ } from "@cunpingtai/webad-react-ad-scripts";
59
+
60
+ <AdsterraBanner placement="home-top" />
61
+ <AdsterraNativeBanner placement="article-middle" />
62
+
63
+ <AdsterraSmartlink className="download-button">
64
+ 继续下载
65
+ </AdsterraSmartlink>
66
+
67
+ <AdsterraSocialBar />
68
+ <AdsterraPopunder />
245
69
  ```
246
70
 
247
- 配置请求、响应格式或组件参数无效时,组件失败关闭,不插入对应广告脚本。
71
+ Banner 和 Native Banner 还支持 `className`、`style`。组件找不到对应配置、类型不匹配或当前策略不允许时会返回 `null`。
248
72
 
249
- ## 脚本域名与 CSP
73
+ ## 策略
250
74
 
251
- 每个组件的 `scriptSrc` 必须:
75
+ | 模式 | Banner / Native | Smartlink | Social Bar / Popunder |
76
+ | ----------------- | --------------- | --------- | --------------------- |
77
+ | `none` | 否 | 否 | 否 |
78
+ | `adsense` | 否 | 否 | 否 |
79
+ | `adsense-limited` | 是 | 否 | 否 |
80
+ | `adsterra-safe` | 是 | 是 | 否 |
81
+ | `adsterra-full` | 是 | 是 | 是 |
252
82
 
253
- - 使用 HTTPS;
254
- - hostname 精确存在于 Worker 的 `ALLOWED_SCRIPT_HOSTS_JSON`;
255
- - 不包含账号密码、显式端口或 URL fragment。
83
+ 同一个 `pageKey` 中,同一个 `placement` 只初始化一次。普通 React re-render 和 Strict Mode 不会重复请求;真实 SPA 路由切换后可以重新初始化。Social Bar 和 Popunder 在整个文档生命周期各只加载一次。
256
84
 
257
- 站点 CSP 至少需要在 `connect-src` 允许 Worker 地址,在 `script-src` 允许实际广告脚本 origin。使用 nonce 时,把同一个 nonce 传给 `AdsProvider`。Smartlink 是链接而不是脚本,它的 hostname 不需要放入脚本 allowlist,除非同一 hostname 也用于加载脚本。
85
+ ## CSP
258
86
 
259
- ## 发布前真实页面验证
87
+ 站点 CSP 的 `connect-src` 需要允许 `https://a1.yumeref.com`,`script-src` 需要允许后台中实际填写的广告脚本域名。`ALLOWED_SCRIPT_HOSTS_JSON` 只约束 Worker 集中加载的 provider 脚本,不约束这些站点广告位脚本。
260
88
 
261
- 第三方广告脚本可能依赖它被插入页面时的执行环境。首次发布前,请在 Adsterra 已批准的测试域名上分别验证五种组件,尤其确认 Banner 的 `invoke.js` 能正常生成广告且没有 `document.write` 错误。未完成这一步前不要发布生产版本。
89
+ 完整的页面布局和广告数量规则见仓库中的 `docs/site-ad-integration-guide.md`。
package/dist/index.d.ts CHANGED
@@ -30,46 +30,30 @@ type AdsProviderProps = {
30
30
  };
31
31
  declare function AdsProvider({ endpoint, pageKey, timeoutMs, nonce, onStatusChange, children, }: AdsProviderProps): react.JSX.Element;
32
32
 
33
- type AdsterraParams = Record<string, string | number | boolean>;
34
-
35
33
  type AdsterraBannerProps = {
36
- slotId: string;
37
- scriptSrc: string;
38
- zoneKey: string;
39
- width: number;
40
- height: number;
41
- params?: AdsterraParams;
34
+ placement: string;
42
35
  className?: string;
43
36
  style?: CSSProperties;
44
37
  };
45
- declare function AdsterraBanner({ slotId, scriptSrc, zoneKey, width, height, params, className, style, }: AdsterraBannerProps): react.JSX.Element | null;
38
+ declare function AdsterraBanner({ placement, className, style, }: AdsterraBannerProps): react.JSX.Element | null;
46
39
 
47
40
  type AdsterraNativeBannerProps = {
48
- slotId: string;
49
- scriptSrc: string;
50
- containerId: string;
41
+ placement: string;
51
42
  className?: string;
52
43
  style?: CSSProperties;
53
44
  };
54
- declare function AdsterraNativeBanner({ slotId, scriptSrc, containerId, className, style, }: AdsterraNativeBannerProps): react.JSX.Element | null;
45
+ declare function AdsterraNativeBanner({ placement, className, style, }: AdsterraNativeBannerProps): react.JSX.Element | null;
55
46
 
56
47
  type AdsterraSmartlinkProps = {
57
- href: string;
58
48
  children: ReactNode;
59
49
  target?: HTMLAttributeAnchorTarget;
60
50
  className?: string;
61
51
  };
62
- declare function AdsterraSmartlink({ href, children, target, className, }: AdsterraSmartlinkProps): react.JSX.Element | null;
52
+ declare function AdsterraSmartlink({ children, target, className, }: AdsterraSmartlinkProps): react.JSX.Element | null;
63
53
 
64
- type AdsterraSocialBarProps = {
65
- slotId: string;
66
- scriptSrc: string;
67
- };
68
- type AdsterraPopunderProps = {
69
- slotId: string;
70
- scriptSrc: string;
71
- };
72
- declare function AdsterraSocialBar(props: AdsterraSocialBarProps): react.JSX.Element;
73
- declare function AdsterraPopunder(props: AdsterraPopunderProps): react.JSX.Element;
54
+ type AdsterraSocialBarProps = Record<string, never>;
55
+ type AdsterraPopunderProps = Record<string, never>;
56
+ declare function AdsterraSocialBar(_props: AdsterraSocialBarProps): react.JSX.Element;
57
+ declare function AdsterraPopunder(_props: AdsterraPopunderProps): react.JSX.Element;
74
58
 
75
59
  export { AdsProvider, type AdsProviderProps, type AdsProviderStatus, AdsterraBanner, type AdsterraBannerProps, AdsterraNativeBanner, type AdsterraNativeBannerProps, AdsterraPopunder, type AdsterraPopunderProps, AdsterraSmartlink, type AdsterraSmartlinkProps, AdsterraSocialBar, type AdsterraSocialBarProps };