@cunpingtai/webad-react-ad-scripts 0.1.0 → 0.2.1

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,83 @@ 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
61
-
62
- 在根路由组件中读取当前 pathname:
63
-
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";
73
-
74
- export const Route = createRootRoute({ component: RootComponent });
75
-
76
- function RootComponent() {
77
- const pathname = useRouterState({
78
- select: (state) => state.location.pathname,
79
- });
80
-
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
- ```
99
-
100
- 不要给 `pageKey` 传 `Date.now()` 或随机数。它应当只在真实 SPA 路由切换时变化。
101
-
102
- ## 广告组件
103
-
104
- Adsterra 提供的 zone key、脚本 URL 和容器 ID 由使用方传入。不要传整段 HTML。
105
-
106
- ### Social Bar
107
-
108
- ```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` 模式显示。
33
+ 把 `AdvertisingProvider` 放在根 layout 中一次。TanStack Router 同理,在根路由读取 `state.location.pathname` 作为 `pageKey`。
118
34
 
119
- ### Native Banner
35
+ `pageKey` 只在真实路由切换时变化。不要传随机数、时间戳、Tab 状态或窗口宽度。
120
36
 
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`。
37
+ 本地开发环境不会显示广告。`localhost`、`*.localhost`、`127.0.0.1` 和 `::1` 不会请求中央配置。
183
38
 
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
- ```
39
+ ## 后台配置
194
40
 
195
- Popunder 在一次完整页面加载中最多初始化一次,仅在 `adsterra-full` 模式显示。
41
+ 打开 `https://a1.yumeref.com/admin`,编辑对应站点:
196
42
 
197
- ## 策略与显示规则
43
+ - Social Bar、Popunder、Smartlink 每站各配置一个;
44
+ - Banner 和 Native Banner 可以添加多个;
45
+ - 当前统一使用三个 Banner 名称:`placement-728x90`、`placement-320x50`、`placement-300x250`;Native Banner 使用 `placement-native`;
46
+ - Banner 填脚本地址、广告 Key、宽度和高度;
47
+ - Native Banner 填脚本地址和 Container ID。
198
48
 
199
- | Worker 返回模式 | Native Banner | Banner | Smartlink | Social Bar | Popunder |
200
- | ----------------- | ------------- | ------ | --------- | ---------- | -------- |
201
- | `none` | 否 | 否 | 否 | 否 | 否 |
202
- | `adsense` | 否 | 否 | 否 | 否 | 否 |
203
- | `adsense-limited` | 是 | 是 | 否 | 否 | 否 |
204
- | `adsterra-safe` | 是 | 是 | 是 | 否 | 否 |
205
- | `adsterra-full` | 是 | 是 | 是 | 是 | 是 |
49
+ 包会用浏览器当前 hostname 自动获取该站点配置,不需要额外传网站标识。
206
50
 
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`。例如:
51
+ ## 组件
231
52
 
232
53
  ```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>
54
+ import {
55
+ AdsterraBanner,
56
+ AdsterraNativeBanner,
57
+ AdsterraPopunder,
58
+ AdsterraSmartlink,
59
+ AdsterraSocialBar,
60
+ } from "@cunpingtai/webad-react-ad-scripts";
61
+
62
+ <AdsterraBanner placement="placement-728x90" />
63
+ <AdsterraBanner placement="placement-300x250" />
64
+ <AdsterraNativeBanner placement="placement-native" />
65
+
66
+ <AdsterraSmartlink className="download-button">
67
+ 继续下载
68
+ </AdsterraSmartlink>
69
+
70
+ <AdsterraSocialBar />
71
+ <AdsterraPopunder />
245
72
  ```
246
73
 
247
- 配置请求、响应格式或组件参数无效时,组件失败关闭,不插入对应广告脚本。
74
+ 这些是后台广告位名称,不是自动识别的尺寸参数;后台配置的尺寸必须与名称对应。同一页面中的同一个 `placement` 只加载一次。Banner 和 Native Banner 还支持 `className`、`style`。组件找不到对应配置、类型不匹配或当前策略不允许时会返回 `null`。
248
75
 
249
- ## 脚本域名与 CSP
76
+ ## 策略
250
77
 
251
- 每个组件的 `scriptSrc` 必须:
78
+ | 模式 | Banner / Native | Smartlink | Social Bar / Popunder |
79
+ | ----------------- | --------------- | --------- | --------------------- |
80
+ | `none` | 否 | 否 | 否 |
81
+ | `adsense` | 否 | 否 | 否 |
82
+ | `adsense-limited` | 是 | 否 | 否 |
83
+ | `adsterra-safe` | 是 | 是 | 否 |
84
+ | `adsterra-full` | 是 | 是 | 是 |
252
85
 
253
- - 使用 HTTPS;
254
- - hostname 精确存在于 Worker 的 `ALLOWED_SCRIPT_HOSTS_JSON`;
255
- - 不包含账号密码、显式端口或 URL fragment。
86
+ 同一个 `pageKey` 中,同一个 `placement` 只初始化一次。普通 React re-render 和 Strict Mode 不会重复请求;真实 SPA 路由切换后可以重新初始化。Social Bar 和 Popunder 在整个文档生命周期各只加载一次。
256
87
 
257
- 站点 CSP 至少需要在 `connect-src` 允许 Worker 地址,在 `script-src` 允许实际广告脚本 origin。使用 nonce 时,把同一个 nonce 传给 `AdsProvider`。Smartlink 是链接而不是脚本,它的 hostname 不需要放入脚本 allowlist,除非同一 hostname 也用于加载脚本。
88
+ ## CSP
258
89
 
259
- ## 发布前真实页面验证
90
+ 站点 CSP 的 `connect-src` 需要允许 `https://a1.yumeref.com`,`script-src` 需要允许后台中实际填写的广告脚本域名。`ALLOWED_SCRIPT_HOSTS_JSON` 只约束 Worker 集中加载的 provider 脚本,不约束这些站点广告位脚本。
260
91
 
261
- 第三方广告脚本可能依赖它被插入页面时的执行环境。首次发布前,请在 Adsterra 已批准的测试域名上分别验证五种组件,尤其确认 Banner 的 `invoke.js` 能正常生成广告且没有 `document.write` 错误。未完成这一步前不要发布生产版本。
92
+ 完整的页面布局和广告数量规则见仓库中的 `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 };