@maccesar/titools 5.1.1 → 5.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/CONTRIBUTING.md +69 -0
- package/README.md +112 -72
- package/agents/ti-pro.md +5 -1
- package/commands/ti-audit.md +5 -1
- package/docs/examples.md +91 -0
- package/docs/knowledge-index.md +57 -0
- package/lib/config.js +2 -0
- package/lib/downloader.js +1 -1
- package/package.json +4 -2
- package/skills/purgetss/references/multi-density-images.md +1 -1
- package/skills/ti-admob-unified/SKILL.md +58 -0
- package/skills/ti-admob-unified/agents/openai.yaml +4 -0
- package/skills/ti-admob-unified/assets/example/app.js +1207 -0
- package/skills/ti-admob-unified/assets/example/lib/ads.js +646 -0
- package/skills/ti-admob-unified/assets/example/library-usage.js +136 -0
- package/skills/ti-admob-unified/references/api-reference.md +615 -0
- package/skills/ti-admob-unified/references/banners.md +435 -0
- package/skills/ti-admob-unified/references/contributing.md +233 -0
- package/skills/ti-admob-unified/references/dependencies.md +91 -0
- package/skills/ti-admob-unified/references/fullscreen.md +238 -0
- package/skills/ti-admob-unified/references/game-monetization.md +148 -0
- package/skills/ti-admob-unified/references/getting-started.md +319 -0
- package/skills/ti-admob-unified/references/index.md +33 -0
- package/skills/ti-admob-unified/references/integration.md +87 -0
- package/skills/ti-admob-unified/references/library.md +220 -0
- package/skills/ti-admob-unified/references/mediation-parity.md +17 -0
- package/skills/ti-admob-unified/references/mediation.md +145 -0
- package/skills/ti-admob-unified/references/migration.md +259 -0
- package/skills/ti-admob-unified/references/module-changelog.md +84 -0
- package/skills/ti-admob-unified/references/module-license.txt +21 -0
- package/skills/ti-admob-unified/references/privacy.md +281 -0
- package/skills/ti-admob-unified/references/production.md +67 -0
- package/skills/ti-admob-unified/references/rewarded-interstitial.md +101 -0
- package/skills/ti-admob-unified/references/roadmap.md +143 -0
- package/skills/ti-admob-unified/references/source-notes.md +80 -0
- package/skills/ti-admob-unified/references/source-snapshot.json +644 -0
- package/skills/ti-admob-unified/references/third-party-notices.md +22 -0
- package/skills/ti-game/SKILL.md +31 -5
- package/skills/ti-game/references/api.md +36 -24
- package/skills/ti-game/references/debugging-performance.md +22 -7
- package/skills/ti-game/references/examples.md +113 -20
- package/skills/ti-game/references/input.md +101 -0
- package/skills/ti-game/references/patterns.md +56 -10
- package/skills/ti-game/references/physics.md +530 -0
- package/skills/ti-game/references/project-setup.md +131 -47
- package/skills/ti-game/references/recipes-action.md +149 -18
- package/skills/ti-game/references/recipes-adventure-effects.md +47 -4
- package/skills/ti-game/references/roadmap.md +11 -4
- package/skills/ti-game/references/source-notes.md +81 -0
- package/skills/ti-game/references/tilemaps.md +13 -4
- package/skills/ti-game-assets/SKILL.md +53 -0
- package/skills/ti-game-assets/agents/openai.yaml +4 -0
- package/skills/ti-game-assets/references/alpha.md +26 -0
- package/skills/ti-game-assets/references/art-direction.md +37 -0
- package/skills/ti-game-assets/references/integration.md +49 -0
- package/skills/ti-game-assets/references/manifest.example.json +429 -0
- package/skills/ti-game-assets/references/manifest.md +39 -0
- package/skills/ti-game-assets/references/offline-audio.md +33 -0
- package/skills/ti-game-assets/references/prompts.md +33 -0
- package/skills/ti-game-assets/references/repeating-textures.md +17 -0
- package/skills/ti-game-assets/references/responsible-ads.md +31 -0
- package/skills/ti-game-assets/references/sound-assets.md +42 -0
- package/skills/ti-game-assets/references/vector-sources.md +21 -0
- package/skills/ti-game-assets/scripts/alpha_tools.py +160 -0
- package/skills/ti-game-assets/scripts/requirements.txt +1 -0
- package/skills/ti-game-assets/scripts/validate_manifest.py +267 -0
- package/skills/ti-synthengine/SKILL.md +77 -67
- package/skills/ti-synthengine/assets/example/README.md +84 -0
- package/skills/ti-synthengine/assets/example/app.js +1178 -0
- package/skills/ti-synthengine/assets/example/lib/sequenceOwner.js +83 -0
- package/skills/ti-synthengine/assets/example/lib/sequenceScore.js +25 -0
- package/skills/ti-synthengine/references/advanced-examples.md +174 -0
- package/skills/ti-synthengine/references/android-sim80-correction-evidence.zip +0 -0
- package/skills/ti-synthengine/references/android-sim80-correction.json +162 -0
- package/skills/ti-synthengine/references/api.md +573 -153
- package/skills/ti-synthengine/references/audio-engine.md +272 -124
- package/skills/ti-synthengine/references/bypass-physical-recheck-evidence.zip +0 -0
- package/skills/ti-synthengine/references/examples.md +355 -106
- package/skills/ti-synthengine/references/frontier-android-evidence.zip +0 -0
- package/skills/ti-synthengine/references/frontier-ipad-evidence.zip +0 -0
- package/skills/ti-synthengine/references/frontier-physical-validation.json +1918 -0
- package/skills/ti-synthengine/references/guide.md +426 -75
- package/skills/ti-synthengine/references/performance-review-evidence.zip +0 -0
- package/skills/ti-synthengine/references/performance-review.json +1146 -0
- package/skills/ti-synthengine/references/physical-validation.json +1457 -0
- package/skills/ti-synthengine/references/recipes-interaction.md +549 -0
- package/skills/ti-synthengine/references/recipes.md +434 -420
- package/skills/ti-synthengine/references/recommendations.md +72 -85
- package/skills/ti-synthengine/references/sound-design.md +375 -155
- package/skills/ti-synthengine/references/source-notes.md +109 -0
- package/skills/ti-synthengine/references/source-snapshot.json +452 -0
- package/skills/ti-synthengine/references/stats-performance.json +2992 -0
- package/skills/ti-synthengine/references/stats-performance.md +82 -0
- package/skills/ti-synthengine/references/studio-validation.json +6202 -0
- package/skills/ti-synthengine/references/troubleshooting.md +123 -78
- package/skills/ti-synthengine/references/unified-bypass-performance.json +22425 -0
- package/skills/ti-synthengine/references/unified-validation.json +314 -0
- package/AGENTS-VERCEL-RESEARCH.md +0 -175
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Copy-ready TiAdMobUnified integration.
|
|
5
|
+
*
|
|
6
|
+
* Alloy: app/lib/ads.js
|
|
7
|
+
* Classic: Resources/lib/ads.js
|
|
8
|
+
*
|
|
9
|
+
* The Google Mobile Ads SDK owns banner refresh. This library never creates a
|
|
10
|
+
* refresh timer and never changes banner visibility while pausing.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const Admob = require('ti.admob.unified')
|
|
14
|
+
|
|
15
|
+
const resources = new Set()
|
|
16
|
+
|
|
17
|
+
let startup = null
|
|
18
|
+
let startupOptions = {}
|
|
19
|
+
let gateResolved = false
|
|
20
|
+
let canRequest = false
|
|
21
|
+
let sdkReady = false
|
|
22
|
+
let privacyOperation = null
|
|
23
|
+
let inspectorOperation = null
|
|
24
|
+
|
|
25
|
+
function call(handler, event) {
|
|
26
|
+
if (typeof handler !== 'function') {
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
handler(event || {})
|
|
32
|
+
} catch (error) {
|
|
33
|
+
Ti.API.error(`[ads] Event handler failed: ${error.message || error}`)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function waitForEvent(name, invoke) {
|
|
38
|
+
return new Promise(resolve => {
|
|
39
|
+
let settled = false
|
|
40
|
+
|
|
41
|
+
function finish(event) {
|
|
42
|
+
if (settled) {
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
settled = true
|
|
47
|
+
Admob.removeEventListener(name, finish)
|
|
48
|
+
resolve(event || {})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
Admob.addEventListener(name, finish)
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
invoke()
|
|
55
|
+
} catch (error) {
|
|
56
|
+
finish({ success: false, error: error.message || String(error) })
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function synchronizeResources(method) {
|
|
62
|
+
Array.from(resources).forEach(resource => {
|
|
63
|
+
if (typeof resource[method] === 'function') {
|
|
64
|
+
resource[method]()
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function initializeIfAllowed() {
|
|
70
|
+
canRequest = Admob.canRequestAds()
|
|
71
|
+
|
|
72
|
+
if (!canRequest) {
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (sdkReady) {
|
|
77
|
+
return true
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const initialized = await waitForEvent('module_initialized', () => {
|
|
81
|
+
Admob.initialize(startupOptions.initialization || {})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
sdkReady = initialized.success === true || initialized.success === 1
|
|
85
|
+
if (!sdkReady) {
|
|
86
|
+
Ti.API.warn(`[ads] Google Mobile Ads initialization failed: ${initialized.error || 'unknown error'}`)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return sdkReady
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Runs UMP -> optional ATT -> eligibility -> SDK initialization once.
|
|
94
|
+
*
|
|
95
|
+
* options.consent: dictionary passed to requestConsent()
|
|
96
|
+
* options.requestTrackingAuthorization: true only when the app has a real ATT use case
|
|
97
|
+
* options.initialization: dictionary passed to initialize()
|
|
98
|
+
* Call start() while active. If UMP spans an iOS interruption, ATT waits for resumed.
|
|
99
|
+
*/
|
|
100
|
+
function start(options) {
|
|
101
|
+
if (startup) {
|
|
102
|
+
return startup
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
startupOptions = options || {}
|
|
106
|
+
startup = (async () => {
|
|
107
|
+
const watchForeground = startupOptions.requestTrackingAuthorization === true && Ti.Platform.osname !== 'android'
|
|
108
|
+
let active = true
|
|
109
|
+
let resumeTracking = null
|
|
110
|
+
function onPause() { active = false }
|
|
111
|
+
function onResumed() {
|
|
112
|
+
active = true
|
|
113
|
+
if (resumeTracking) resumeTracking()
|
|
114
|
+
}
|
|
115
|
+
if (watchForeground) {
|
|
116
|
+
Ti.App.addEventListener('pause', onPause)
|
|
117
|
+
Ti.App.addEventListener('resumed', onResumed)
|
|
118
|
+
}
|
|
119
|
+
const consent = await waitForEvent('consent_complete', () => {
|
|
120
|
+
Admob.requestConsent(startupOptions.consent || {})
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
if (consent.success === false || consent.success === 0) {
|
|
124
|
+
Ti.API.warn(`[ads] UMP completed with an error: ${consent.error || 'unknown error'}`)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let tracking = null
|
|
128
|
+
if (startupOptions.requestTrackingAuthorization === true) {
|
|
129
|
+
if (watchForeground) {
|
|
130
|
+
if (!active) await new Promise(resolve => { resumeTracking = resolve })
|
|
131
|
+
Ti.App.removeEventListener('pause', onPause)
|
|
132
|
+
Ti.App.removeEventListener('resumed', onResumed)
|
|
133
|
+
}
|
|
134
|
+
tracking = await waitForEvent('tracking_authorization_completed', () => {
|
|
135
|
+
Admob.requestTrackingAuthorization()
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
await initializeIfAllowed()
|
|
140
|
+
gateResolved = true
|
|
141
|
+
synchronizeResources('sync')
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
consent,
|
|
145
|
+
tracking,
|
|
146
|
+
canRequestAds: canRequest,
|
|
147
|
+
initialized: sdkReady
|
|
148
|
+
}
|
|
149
|
+
})()
|
|
150
|
+
|
|
151
|
+
return startup
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function addListener(proxy, listeners, name, handler) {
|
|
155
|
+
if (!name || typeof handler !== 'function') {
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
proxy.addEventListener(name, handler)
|
|
160
|
+
listeners.push([name, handler])
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function removeListeners(proxy, listeners) {
|
|
164
|
+
listeners.forEach(entry => proxy.removeEventListener(entry[0], entry[1]))
|
|
165
|
+
listeners.length = 0
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function bannerProperties(options, adaptive) {
|
|
169
|
+
const properties = {
|
|
170
|
+
adUnitId: options.adUnitId,
|
|
171
|
+
width: options.width || Ti.UI.FILL
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
for (const key of ['keywords', 'contentUrl', 'requestAgent', 'extras']) {
|
|
175
|
+
if (options[key] !== undefined) {
|
|
176
|
+
properties[key] = options[key]
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (adaptive) {
|
|
181
|
+
if (Ti.Platform && Ti.Platform.osname !== 'android') properties.height = Ti.UI.FILL
|
|
182
|
+
properties.adaptiveType = options.adaptiveType || Admob.ADAPTIVE_ANCHORED
|
|
183
|
+
for (const key of ['maxHeight', 'collapsible']) {
|
|
184
|
+
if (options[key] !== undefined) {
|
|
185
|
+
properties[key] = options[key]
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
properties.adSize = options.adSize || Admob.BANNER
|
|
190
|
+
if (options.customAdSize !== undefined) {
|
|
191
|
+
properties.customAdSize = options.customAdSize
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return properties
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function createBannerPlacement(options, adaptive) {
|
|
199
|
+
if (!options || !options.host || !options.adUnitId) {
|
|
200
|
+
throw new Error('A banner requires host and adUnitId')
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const host = options.host
|
|
204
|
+
const content = options.content
|
|
205
|
+
const reservedHeight = Number(options.reservedHeight)
|
|
206
|
+
|
|
207
|
+
if (!Number.isFinite(reservedHeight) || reservedHeight <= 0) {
|
|
208
|
+
throw new Error('A banner requires a positive reservedHeight in dp')
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let proxy = null
|
|
212
|
+
let listeners = []
|
|
213
|
+
let destroyed = false
|
|
214
|
+
let locallyPaused = options.paused === true
|
|
215
|
+
|
|
216
|
+
function reserve(height) {
|
|
217
|
+
const dimension = `${Math.max(0, Number(height) || 0)}dp`
|
|
218
|
+
host.height = dimension
|
|
219
|
+
if (content) {
|
|
220
|
+
content.bottom = dimension
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function detach(destroyNative) {
|
|
225
|
+
if (!proxy) {
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const target = proxy
|
|
230
|
+
proxy = null
|
|
231
|
+
removeListeners(target, listeners)
|
|
232
|
+
|
|
233
|
+
if (destroyNative) {
|
|
234
|
+
target.destroy()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
host.remove(target)
|
|
239
|
+
} catch (error) {
|
|
240
|
+
Ti.API.debug(`[ads] Banner was already detached: ${error.message || error}`)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function onLoaded(event) {
|
|
245
|
+
const actualHeight = Number(event.height)
|
|
246
|
+
reserve(actualHeight > 0 ? actualHeight : reservedHeight)
|
|
247
|
+
call(options.onLoaded, event)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function onFailedToLoad(event) {
|
|
251
|
+
detach(true)
|
|
252
|
+
if (options.collapseOnFailure !== false) {
|
|
253
|
+
reserve(0)
|
|
254
|
+
}
|
|
255
|
+
call(options.onFailedToLoad, event)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function onDestroyed(event) {
|
|
259
|
+
detach(false)
|
|
260
|
+
reserve(0)
|
|
261
|
+
call(options.onDestroyed, event)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function attachForwarder(name, optionName) {
|
|
265
|
+
addListener(proxy, listeners, name, event => call(options[optionName], event))
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const placement = {
|
|
269
|
+
sync() {
|
|
270
|
+
if (destroyed || locallyPaused || proxy) {
|
|
271
|
+
return
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (!canRequest || !sdkReady) {
|
|
275
|
+
if (gateResolved) {
|
|
276
|
+
reserve(0)
|
|
277
|
+
}
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
reserve(reservedHeight)
|
|
282
|
+
proxy = adaptive
|
|
283
|
+
? Admob.createAdaptiveBanner(bannerProperties(options, true))
|
|
284
|
+
: Admob.createBanner(bannerProperties(options, false))
|
|
285
|
+
|
|
286
|
+
addListener(proxy, listeners, Admob.AD_LOADED, onLoaded)
|
|
287
|
+
addListener(proxy, listeners, Admob.AD_FAILED_TO_LOAD, onFailedToLoad)
|
|
288
|
+
addListener(proxy, listeners, Admob.AD_DESTROYED, onDestroyed)
|
|
289
|
+
attachForwarder(Admob.AD_CLICKED, 'onClicked')
|
|
290
|
+
attachForwarder(Admob.AD_IMPRESSION, 'onImpression')
|
|
291
|
+
attachForwarder(Admob.AD_OPENED, 'onOpened')
|
|
292
|
+
attachForwarder(Admob.AD_CLOSED, 'onClosed')
|
|
293
|
+
|
|
294
|
+
if (adaptive && Ti.Platform && Ti.Platform.osname !== 'android') {
|
|
295
|
+
const target = proxy
|
|
296
|
+
const anchored = !options.adaptiveType || options.adaptiveType === Admob.ADAPTIVE_ANCHORED
|
|
297
|
+
let measuredWidth = 0
|
|
298
|
+
function prepareSize(width) {
|
|
299
|
+
if (!anchored || width <= 0 || measuredWidth === width) return false
|
|
300
|
+
measuredWidth = width
|
|
301
|
+
const height = Admob.getAnchoredAdaptiveBannerSize(width).height
|
|
302
|
+
const changed = host.height !== `${height}dp`
|
|
303
|
+
reserve(height)
|
|
304
|
+
return changed
|
|
305
|
+
}
|
|
306
|
+
prepareSize(host.rect ? host.rect.width : 0)
|
|
307
|
+
function loadAfterLayout() {
|
|
308
|
+
if (destroyed || proxy !== target || target.rect.width <= 0 || target.rect.height <= 0) return
|
|
309
|
+
if (prepareSize(target.rect.width)) return
|
|
310
|
+
target.removeEventListener('postlayout', loadAfterLayout)
|
|
311
|
+
target.load()
|
|
312
|
+
}
|
|
313
|
+
addListener(target, listeners, 'postlayout', loadAfterLayout)
|
|
314
|
+
host.add(target)
|
|
315
|
+
} else {
|
|
316
|
+
host.add(proxy)
|
|
317
|
+
proxy.load()
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
pause() {
|
|
322
|
+
if (destroyed || locallyPaused) {
|
|
323
|
+
return
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
locallyPaused = true
|
|
327
|
+
if (proxy) {
|
|
328
|
+
proxy.pause()
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
resume() {
|
|
333
|
+
if (destroyed || !locallyPaused) {
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
locallyPaused = false
|
|
338
|
+
if (proxy) {
|
|
339
|
+
proxy.resume()
|
|
340
|
+
}
|
|
341
|
+
placement.sync()
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
rebuild() {
|
|
345
|
+
if (destroyed) {
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
detach(true)
|
|
350
|
+
reserve(reservedHeight)
|
|
351
|
+
placement.sync()
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
retry() {
|
|
355
|
+
placement.rebuild()
|
|
356
|
+
},
|
|
357
|
+
|
|
358
|
+
authorizationChanged() {
|
|
359
|
+
if (destroyed) {
|
|
360
|
+
return
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
detach(true)
|
|
364
|
+
reserve(canRequest && sdkReady ? reservedHeight : 0)
|
|
365
|
+
placement.sync()
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
isLoaded() {
|
|
369
|
+
return Boolean(proxy && proxy.isLoaded())
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
destroy() {
|
|
373
|
+
if (destroyed) {
|
|
374
|
+
return
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
destroyed = true
|
|
378
|
+
resources.delete(placement)
|
|
379
|
+
detach(true)
|
|
380
|
+
reserve(0)
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
resources.add(placement)
|
|
385
|
+
reserve(reservedHeight)
|
|
386
|
+
placement.sync()
|
|
387
|
+
return placement
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function createBanner(options) {
|
|
391
|
+
return createBannerPlacement(options, false)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function createAdaptiveBanner(options) {
|
|
395
|
+
return createBannerPlacement(options, true)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const fullscreenEvents = [
|
|
399
|
+
['AD_LOADED', 'onLoaded'],
|
|
400
|
+
['AD_FAILED_TO_LOAD', 'onFailedToLoad'],
|
|
401
|
+
['AD_OPENED', 'onOpened'],
|
|
402
|
+
['AD_SHOWED_FULLSCREEN_CONTENT', 'onShowed'],
|
|
403
|
+
['AD_CLOSED', 'onClosed'],
|
|
404
|
+
['AD_CLICKED', 'onClicked'],
|
|
405
|
+
['AD_IMPRESSION', 'onImpression'],
|
|
406
|
+
['AD_FAILED_TO_SHOW', 'onFailedToShow'],
|
|
407
|
+
['AD_REWARDED', 'onRewarded'],
|
|
408
|
+
['AD_DESTROYED', 'onDestroyed']
|
|
409
|
+
]
|
|
410
|
+
|
|
411
|
+
function fullscreenProperties(options) {
|
|
412
|
+
const properties = {
|
|
413
|
+
adUnitId: options.adUnitId,
|
|
414
|
+
autoReload: options.autoReload === true
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
for (const key of ['keywords', 'requestAgent', 'extras']) {
|
|
418
|
+
if (options[key] !== undefined) {
|
|
419
|
+
properties[key] = options[key]
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return properties
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Shared by the managers and the raw-API playground. Rotation alone does no work.
|
|
427
|
+
function createFullscreenSizeGuard(getSize) {
|
|
428
|
+
let requestedSize = null
|
|
429
|
+
function sizeKey() {
|
|
430
|
+
const geometry = getSize()
|
|
431
|
+
// Window.size can change while Android restores bars after a fullscreen ad.
|
|
432
|
+
// Outer window bounds stay stable and still track rotation and split view.
|
|
433
|
+
const rect = geometry && geometry.rect
|
|
434
|
+
const size = rect && rect.width > 0 && rect.height > 0
|
|
435
|
+
? rect : (geometry && geometry.size) || geometry
|
|
436
|
+
return size && size.width > 0 && size.height > 0
|
|
437
|
+
? `${Math.round(size.width)}x${Math.round(size.height)}` : null
|
|
438
|
+
}
|
|
439
|
+
return {
|
|
440
|
+
recordLoad() { requestedSize = sizeKey() },
|
|
441
|
+
show(present, reload) {
|
|
442
|
+
const currentSize = sizeKey()
|
|
443
|
+
if (currentSize && currentSize !== requestedSize) {
|
|
444
|
+
reload()
|
|
445
|
+
return false
|
|
446
|
+
}
|
|
447
|
+
present()
|
|
448
|
+
return true
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function createFullscreen(options, factoryName) {
|
|
454
|
+
if (!options || !options.adUnitId) {
|
|
455
|
+
throw new Error(`${factoryName} requires adUnitId`)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
let proxy = null
|
|
459
|
+
let listeners = []
|
|
460
|
+
let destroyed = false
|
|
461
|
+
let loadRequested = options.autoLoad === true
|
|
462
|
+
let loading = false
|
|
463
|
+
const sizeGuard = createFullscreenSizeGuard(() => options.window || Ti.UI.currentWindow)
|
|
464
|
+
|
|
465
|
+
function detach(destroyNative) {
|
|
466
|
+
if (!proxy) {
|
|
467
|
+
return
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const target = proxy
|
|
471
|
+
proxy = null
|
|
472
|
+
loading = false
|
|
473
|
+
removeListeners(target, listeners)
|
|
474
|
+
if (destroyNative) {
|
|
475
|
+
target.destroy()
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const manager = {
|
|
480
|
+
sync() {
|
|
481
|
+
if (destroyed || proxy || !loadRequested || !canRequest || !sdkReady) {
|
|
482
|
+
return
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
proxy = Admob[factoryName](fullscreenProperties(options))
|
|
486
|
+
|
|
487
|
+
fullscreenEvents.forEach(entry => {
|
|
488
|
+
const eventName = Admob[entry[0]]
|
|
489
|
+
if (!eventName) {
|
|
490
|
+
return
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
addListener(proxy, listeners, eventName, event => {
|
|
494
|
+
if (entry[0] === 'AD_LOADED' || entry[0] === 'AD_FAILED_TO_LOAD') loading = false
|
|
495
|
+
if (entry[0] === 'AD_CLOSED' && options.autoReload === true) {
|
|
496
|
+
loading = true
|
|
497
|
+
sizeGuard.recordLoad()
|
|
498
|
+
}
|
|
499
|
+
if (entry[0] === 'AD_CLOSED' && options.autoReload !== true) {
|
|
500
|
+
loadRequested = false
|
|
501
|
+
}
|
|
502
|
+
if (entry[0] === 'AD_FAILED_TO_SHOW') {
|
|
503
|
+
loadRequested = false
|
|
504
|
+
}
|
|
505
|
+
if (entry[0] === 'AD_DESTROYED') {
|
|
506
|
+
detach(false)
|
|
507
|
+
call(options[entry[1]], event)
|
|
508
|
+
return
|
|
509
|
+
}
|
|
510
|
+
call(options[entry[1]], event)
|
|
511
|
+
})
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
loading = true
|
|
515
|
+
sizeGuard.recordLoad()
|
|
516
|
+
proxy.load()
|
|
517
|
+
},
|
|
518
|
+
|
|
519
|
+
load() {
|
|
520
|
+
if (destroyed || loading) {
|
|
521
|
+
return false
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
loadRequested = true
|
|
525
|
+
if (proxy) {
|
|
526
|
+
loading = true
|
|
527
|
+
sizeGuard.recordLoad()
|
|
528
|
+
proxy.load()
|
|
529
|
+
} else {
|
|
530
|
+
manager.sync()
|
|
531
|
+
}
|
|
532
|
+
return true
|
|
533
|
+
},
|
|
534
|
+
|
|
535
|
+
show() {
|
|
536
|
+
if (!proxy || loading || !proxy.isLoaded()) {
|
|
537
|
+
return false
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return sizeGuard.show(() => proxy.show(), () => manager.retry())
|
|
541
|
+
},
|
|
542
|
+
|
|
543
|
+
isLoaded() {
|
|
544
|
+
return Boolean(proxy && proxy.isLoaded())
|
|
545
|
+
},
|
|
546
|
+
|
|
547
|
+
retry() {
|
|
548
|
+
if (destroyed) {
|
|
549
|
+
return false
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
detach(true)
|
|
553
|
+
loadRequested = true
|
|
554
|
+
manager.sync()
|
|
555
|
+
return true
|
|
556
|
+
},
|
|
557
|
+
|
|
558
|
+
authorizationChanged() {
|
|
559
|
+
if (destroyed) {
|
|
560
|
+
return
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
detach(true)
|
|
564
|
+
manager.sync()
|
|
565
|
+
},
|
|
566
|
+
|
|
567
|
+
destroy() {
|
|
568
|
+
if (destroyed) {
|
|
569
|
+
return
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
destroyed = true
|
|
573
|
+
resources.delete(manager)
|
|
574
|
+
detach(true)
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
resources.add(manager)
|
|
579
|
+
manager.sync()
|
|
580
|
+
return manager
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function createInterstitial(options) {
|
|
584
|
+
return createFullscreen(options, 'createInterstitial')
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function createRewarded(options) {
|
|
588
|
+
return createFullscreen(options, 'createRewarded')
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function createRewardedInterstitial(options) {
|
|
592
|
+
return createFullscreen(options, 'createRewardedInterstitial')
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function createAppOpen(options) {
|
|
596
|
+
return createFullscreen(options, 'createAppOpen')
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function showPrivacyOptions() {
|
|
600
|
+
if (privacyOperation) return privacyOperation
|
|
601
|
+
privacyOperation = (async () => {
|
|
602
|
+
const result = await waitForEvent('privacy_options_dismissed', () => {
|
|
603
|
+
Admob.presentPrivacyOptions({})
|
|
604
|
+
})
|
|
605
|
+
|
|
606
|
+
await initializeIfAllowed()
|
|
607
|
+
synchronizeResources('authorizationChanged')
|
|
608
|
+
return result
|
|
609
|
+
})().finally(() => { privacyOperation = null })
|
|
610
|
+
return privacyOperation
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function isPrivacyOptionsRequired() {
|
|
614
|
+
return Admob.isPrivacyOptionsRequired()
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function openAdInspector() {
|
|
618
|
+
if (inspectorOperation) return inspectorOperation
|
|
619
|
+
inspectorOperation = waitForEvent('ad_inspector_closed', () => Admob.openAdInspector())
|
|
620
|
+
.finally(() => { inspectorOperation = null })
|
|
621
|
+
return inspectorOperation
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function isReady() {
|
|
625
|
+
return canRequest && sdkReady
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function destroyAll() {
|
|
629
|
+
Array.from(resources).forEach(resource => resource.destroy())
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
module.exports = {
|
|
633
|
+
start,
|
|
634
|
+
isReady,
|
|
635
|
+
isPrivacyOptionsRequired,
|
|
636
|
+
showPrivacyOptions,
|
|
637
|
+
openAdInspector,
|
|
638
|
+
createBanner,
|
|
639
|
+
createAdaptiveBanner,
|
|
640
|
+
createFullscreenSizeGuard,
|
|
641
|
+
createInterstitial,
|
|
642
|
+
createRewarded,
|
|
643
|
+
createRewardedInterstitial,
|
|
644
|
+
createAppOpen,
|
|
645
|
+
destroyAll
|
|
646
|
+
}
|