@jskit-ai/google-rewarded-core 0.1.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 +98 -0
- package/docs/protecting-server-actions.md +190 -0
- package/package.descriptor.mjs +186 -0
- package/package.json +10 -0
- package/src/server/GoogleRewardedCoreProvider.js +51 -0
- package/src/server/actions.js +102 -0
- package/src/server/inputSchemas.js +394 -0
- package/src/server/providerConfigs/GoogleRewardedProviderConfigsProvider.js +98 -0
- package/src/server/providerConfigs/actions.js +157 -0
- package/src/server/providerConfigs/registerRoutes.js +176 -0
- package/src/server/providerConfigs/repository.js +107 -0
- package/src/server/providerConfigs/service.js +61 -0
- package/src/server/registerRoutes.js +169 -0
- package/src/server/rules/GoogleRewardedRulesProvider.js +98 -0
- package/src/server/rules/actions.js +157 -0
- package/src/server/rules/registerRoutes.js +176 -0
- package/src/server/rules/repository.js +107 -0
- package/src/server/rules/service.js +61 -0
- package/src/server/service.js +552 -0
- package/src/server/support/requireGoogleRewardedUnlock.js +142 -0
- package/src/server/unlockReceipts/GoogleRewardedUnlockReceiptsProvider.js +98 -0
- package/src/server/unlockReceipts/actions.js +157 -0
- package/src/server/unlockReceipts/registerRoutes.js +176 -0
- package/src/server/unlockReceipts/repository.js +107 -0
- package/src/server/unlockReceipts/service.js +61 -0
- package/src/server/watchSessions/GoogleRewardedWatchSessionsProvider.js +98 -0
- package/src/server/watchSessions/actions.js +157 -0
- package/src/server/watchSessions/registerRoutes.js +176 -0
- package/src/server/watchSessions/repository.js +107 -0
- package/src/server/watchSessions/service.js +61 -0
- package/src/shared/googleRewardedProviderConfigResource.js +94 -0
- package/src/shared/googleRewardedRuleResource.js +136 -0
- package/src/shared/googleRewardedUnlockReceiptResource.js +113 -0
- package/src/shared/googleRewardedWatchSessionResource.js +139 -0
- package/src/shared/index.js +12 -0
- package/templates/migrations/google_rewarded_provider_configs_initial.cjs +27 -0
- package/templates/migrations/google_rewarded_rules_initial.cjs +31 -0
- package/templates/migrations/google_rewarded_unlock_receipts_initial.cjs +35 -0
- package/templates/migrations/google_rewarded_watch_sessions_initial.cjs +36 -0
- package/test/requireGoogleRewardedUnlock.test.js +274 -0
- package/test/routes.test.js +248 -0
- package/test/service.test.js +315 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { createService } from "../src/server/service.js";
|
|
5
|
+
|
|
6
|
+
function createRecordDocument(type, id, attributes = {}) {
|
|
7
|
+
return {
|
|
8
|
+
data: {
|
|
9
|
+
type,
|
|
10
|
+
id: String(id),
|
|
11
|
+
attributes: {
|
|
12
|
+
...attributes
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function createCollectionDocument(type, rows = []) {
|
|
19
|
+
return {
|
|
20
|
+
data: rows.map((entry, index) => ({
|
|
21
|
+
type,
|
|
22
|
+
id: String(entry?.id ?? index + 1),
|
|
23
|
+
attributes: {
|
|
24
|
+
...entry
|
|
25
|
+
}
|
|
26
|
+
}))
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function createRuleRepository(rows = []) {
|
|
31
|
+
return {
|
|
32
|
+
async queryDocuments() {
|
|
33
|
+
return createCollectionDocument("googleRewardedRules", rows);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function createProviderConfigRepository(rows = []) {
|
|
39
|
+
return {
|
|
40
|
+
async queryDocuments() {
|
|
41
|
+
return createCollectionDocument("googleRewardedProviderConfigs", rows);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function createUnlockReceiptRepository(rows = [], createdRecord = null) {
|
|
47
|
+
return {
|
|
48
|
+
async queryDocuments() {
|
|
49
|
+
return createCollectionDocument("googleRewardedUnlockReceipts", rows);
|
|
50
|
+
},
|
|
51
|
+
async createDocument(payload) {
|
|
52
|
+
return createRecordDocument("googleRewardedUnlockReceipts", createdRecord?.id || "301", {
|
|
53
|
+
...(createdRecord || {}),
|
|
54
|
+
...payload
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function createWatchSessionRepository({
|
|
61
|
+
session = null,
|
|
62
|
+
patchedSession = null
|
|
63
|
+
} = {}) {
|
|
64
|
+
const calls = {
|
|
65
|
+
patches: []
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
calls,
|
|
70
|
+
async queryDocuments() {
|
|
71
|
+
return createCollectionDocument("googleRewardedWatchSessions", []);
|
|
72
|
+
},
|
|
73
|
+
async getDocumentById() {
|
|
74
|
+
return session ? createRecordDocument("googleRewardedWatchSessions", session.id, session) : null;
|
|
75
|
+
},
|
|
76
|
+
async patchDocumentById(recordId, patch) {
|
|
77
|
+
calls.patches.push({
|
|
78
|
+
recordId,
|
|
79
|
+
patch
|
|
80
|
+
});
|
|
81
|
+
return createRecordDocument("googleRewardedWatchSessions", patchedSession?.id || recordId, {
|
|
82
|
+
...(patchedSession || session || {}),
|
|
83
|
+
...patch
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
withTransaction(work) {
|
|
87
|
+
return work("trx-1");
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const REQUEST_CONTEXT = Object.freeze({
|
|
93
|
+
requestMeta: {
|
|
94
|
+
request: {
|
|
95
|
+
user: {
|
|
96
|
+
id: "7"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("getCurrentState reports a blocked gate when rule and provider config exist without an active unlock", async () => {
|
|
103
|
+
const service = createService({
|
|
104
|
+
googleRewardedRulesRepository: createRuleRepository([
|
|
105
|
+
{
|
|
106
|
+
id: "11",
|
|
107
|
+
gateKey: "progress-logging",
|
|
108
|
+
surface: "app",
|
|
109
|
+
enabled: true,
|
|
110
|
+
unlockMinutes: 30,
|
|
111
|
+
cooldownMinutes: 0,
|
|
112
|
+
dailyLimit: null,
|
|
113
|
+
title: "Log your workout",
|
|
114
|
+
description: "Watch an ad to log progress."
|
|
115
|
+
}
|
|
116
|
+
]),
|
|
117
|
+
googleRewardedProviderConfigsRepository: createProviderConfigRepository([
|
|
118
|
+
{
|
|
119
|
+
id: "21",
|
|
120
|
+
surface: "app",
|
|
121
|
+
enabled: true,
|
|
122
|
+
adUnitPath: "/123456/rewarded",
|
|
123
|
+
scriptMode: "gpt_rewarded"
|
|
124
|
+
}
|
|
125
|
+
]),
|
|
126
|
+
googleRewardedWatchSessionsRepository: createWatchSessionRepository(),
|
|
127
|
+
googleRewardedUnlockReceiptsRepository: createUnlockReceiptRepository([])
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const response = await service.getCurrentState(
|
|
131
|
+
{
|
|
132
|
+
workspaceSlug: "alpha",
|
|
133
|
+
gateKey: "progress-logging"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
context: REQUEST_CONTEXT
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
assert.equal(response.enabled, true);
|
|
141
|
+
assert.equal(response.available, true);
|
|
142
|
+
assert.equal(response.blocked, true);
|
|
143
|
+
assert.equal(response.reason, "reward-required");
|
|
144
|
+
assert.equal(response.providerConfig.adUnitPath, "/123456/rewarded");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("getCurrentState reuses an active unlock receipt instead of blocking", async () => {
|
|
148
|
+
const futureDate = new Date(Date.now() + 15 * 60 * 1000).toISOString();
|
|
149
|
+
const service = createService({
|
|
150
|
+
googleRewardedRulesRepository: createRuleRepository([
|
|
151
|
+
{
|
|
152
|
+
id: "11",
|
|
153
|
+
gateKey: "progress-logging",
|
|
154
|
+
surface: "app",
|
|
155
|
+
enabled: true,
|
|
156
|
+
unlockMinutes: 30,
|
|
157
|
+
cooldownMinutes: 0,
|
|
158
|
+
dailyLimit: null,
|
|
159
|
+
title: "Log your workout",
|
|
160
|
+
description: "Watch an ad to log progress."
|
|
161
|
+
}
|
|
162
|
+
]),
|
|
163
|
+
googleRewardedProviderConfigsRepository: createProviderConfigRepository([
|
|
164
|
+
{
|
|
165
|
+
id: "21",
|
|
166
|
+
surface: "app",
|
|
167
|
+
enabled: true,
|
|
168
|
+
adUnitPath: "/123456/rewarded",
|
|
169
|
+
scriptMode: "gpt_rewarded"
|
|
170
|
+
}
|
|
171
|
+
]),
|
|
172
|
+
googleRewardedWatchSessionsRepository: createWatchSessionRepository(),
|
|
173
|
+
googleRewardedUnlockReceiptsRepository: createUnlockReceiptRepository([
|
|
174
|
+
{
|
|
175
|
+
id: "31",
|
|
176
|
+
gateKey: "progress-logging",
|
|
177
|
+
providerConfigId: "21",
|
|
178
|
+
watchSessionId: "41",
|
|
179
|
+
grantedAt: new Date().toISOString(),
|
|
180
|
+
unlockedUntil: futureDate
|
|
181
|
+
}
|
|
182
|
+
])
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const response = await service.getCurrentState(
|
|
186
|
+
{
|
|
187
|
+
workspaceSlug: "alpha",
|
|
188
|
+
gateKey: "progress-logging"
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
context: REQUEST_CONTEXT
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
assert.equal(response.blocked, false);
|
|
196
|
+
assert.equal(response.reason, "already-unlocked");
|
|
197
|
+
assert.equal(response.unlock.unlockedUntil, futureDate);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("getCurrentState ignores caller surface overrides and evaluates the app-surface rule", async () => {
|
|
201
|
+
const recordedRuleQueries = [];
|
|
202
|
+
const recordedConfigQueries = [];
|
|
203
|
+
const service = createService({
|
|
204
|
+
googleRewardedRulesRepository: {
|
|
205
|
+
async queryDocuments(query) {
|
|
206
|
+
recordedRuleQueries.push(query);
|
|
207
|
+
return createCollectionDocument("googleRewardedRules", [
|
|
208
|
+
{
|
|
209
|
+
id: "11",
|
|
210
|
+
gateKey: "progress-logging",
|
|
211
|
+
surface: "app",
|
|
212
|
+
enabled: true,
|
|
213
|
+
unlockMinutes: 30,
|
|
214
|
+
cooldownMinutes: 0,
|
|
215
|
+
dailyLimit: null,
|
|
216
|
+
title: "Log your workout",
|
|
217
|
+
description: "Watch an ad to log progress."
|
|
218
|
+
}
|
|
219
|
+
]);
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
googleRewardedProviderConfigsRepository: {
|
|
223
|
+
async queryDocuments(query) {
|
|
224
|
+
recordedConfigQueries.push(query);
|
|
225
|
+
return createCollectionDocument("googleRewardedProviderConfigs", [
|
|
226
|
+
{
|
|
227
|
+
id: "21",
|
|
228
|
+
surface: "app",
|
|
229
|
+
enabled: true,
|
|
230
|
+
adUnitPath: "/123456/rewarded",
|
|
231
|
+
scriptMode: "gpt_rewarded"
|
|
232
|
+
}
|
|
233
|
+
]);
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
googleRewardedWatchSessionsRepository: createWatchSessionRepository(),
|
|
237
|
+
googleRewardedUnlockReceiptsRepository: createUnlockReceiptRepository([])
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const response = await service.getCurrentState(
|
|
241
|
+
{
|
|
242
|
+
workspaceSlug: "alpha",
|
|
243
|
+
gateKey: "progress-logging",
|
|
244
|
+
surface: "admin"
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
context: REQUEST_CONTEXT
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
assert.equal(recordedRuleQueries[0]?.surface, "app");
|
|
252
|
+
assert.equal(recordedConfigQueries[0]?.surface, "app");
|
|
253
|
+
assert.equal(response.surface, "app");
|
|
254
|
+
assert.equal(response.reason, "reward-required");
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("grantReward patches the watch session and creates an unlock receipt", async () => {
|
|
258
|
+
const watchSessionsRepository = createWatchSessionRepository({
|
|
259
|
+
session: {
|
|
260
|
+
id: "41",
|
|
261
|
+
gateKey: "progress-logging",
|
|
262
|
+
providerConfigId: "21",
|
|
263
|
+
status: "started",
|
|
264
|
+
startedAt: new Date().toISOString()
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
const createdReceiptPayloads = [];
|
|
268
|
+
const unlockReceiptsRepository = {
|
|
269
|
+
async queryDocuments() {
|
|
270
|
+
return createCollectionDocument("googleRewardedUnlockReceipts", []);
|
|
271
|
+
},
|
|
272
|
+
async createDocument(payload) {
|
|
273
|
+
createdReceiptPayloads.push(payload);
|
|
274
|
+
return createRecordDocument("googleRewardedUnlockReceipts", "51", payload);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const service = createService({
|
|
279
|
+
googleRewardedRulesRepository: createRuleRepository([
|
|
280
|
+
{
|
|
281
|
+
id: "11",
|
|
282
|
+
gateKey: "progress-logging",
|
|
283
|
+
surface: "app",
|
|
284
|
+
enabled: true,
|
|
285
|
+
unlockMinutes: 45,
|
|
286
|
+
cooldownMinutes: 0,
|
|
287
|
+
dailyLimit: null,
|
|
288
|
+
title: "Log your workout",
|
|
289
|
+
description: "Watch an ad to log progress."
|
|
290
|
+
}
|
|
291
|
+
]),
|
|
292
|
+
googleRewardedProviderConfigsRepository: createProviderConfigRepository([]),
|
|
293
|
+
googleRewardedWatchSessionsRepository: watchSessionsRepository,
|
|
294
|
+
googleRewardedUnlockReceiptsRepository: unlockReceiptsRepository
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const response = await service.grantReward(
|
|
298
|
+
{
|
|
299
|
+
workspaceSlug: "alpha",
|
|
300
|
+
sessionId: "41"
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
context: REQUEST_CONTEXT
|
|
304
|
+
}
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
assert.equal(response.unlocked, true);
|
|
308
|
+
assert.equal(response.gateKey, "progress-logging");
|
|
309
|
+
assert.equal(createdReceiptPayloads.length, 1);
|
|
310
|
+
assert.equal(createdReceiptPayloads[0].watchSessionId, "41");
|
|
311
|
+
assert.equal(createdReceiptPayloads[0].providerConfigId, "21");
|
|
312
|
+
assert.equal(watchSessionsRepository.calls.patches.length, 1);
|
|
313
|
+
assert.equal(watchSessionsRepository.calls.patches[0].recordId, "41");
|
|
314
|
+
assert.equal(watchSessionsRepository.calls.patches[0].patch.status, "rewarded");
|
|
315
|
+
});
|