@jskit-ai/rewarded-web 0.1.120

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.
@@ -0,0 +1,325 @@
1
+ import { reactive } from "vue";
2
+ import { createHttpClient } from "@jskit-ai/http-runtime/client";
3
+
4
+ const REWARDED_RUNTIME_INJECTION_KEY = Symbol("rewarded.web.runtime");
5
+ const REWARDED_CONFIGURATION_REASONS = new Set([
6
+ "rule-not-configured",
7
+ "provider-not-configured"
8
+ ]);
9
+ const REWARDED_NON_BLOCKING_REASONS = new Set([
10
+ "already-unlocked",
11
+ "cooldown-active",
12
+ "daily-limit-reached",
13
+ ...REWARDED_CONFIGURATION_REASONS
14
+ ]);
15
+ const rewardedHttpClient = createHttpClient({
16
+ credentials: "include",
17
+ csrf: {
18
+ sessionPath: "/api/session"
19
+ }
20
+ });
21
+
22
+ function createInitialState() {
23
+ return {
24
+ open: false,
25
+ phase: "idle",
26
+ errorMessage: "",
27
+ gateState: null,
28
+ session: null,
29
+ request: null
30
+ };
31
+ }
32
+
33
+ function applyState(target, source) {
34
+ for (const key of Object.keys(target)) {
35
+ if (!Object.hasOwn(source, key)) {
36
+ delete target[key];
37
+ }
38
+ }
39
+ for (const [key, value] of Object.entries(source)) {
40
+ target[key] = value;
41
+ }
42
+ }
43
+
44
+ function normalizeWorkspaceSlug(value = "") {
45
+ return String(value || "").trim().toLowerCase();
46
+ }
47
+
48
+ function isWellFormedGateState(gateState = null) {
49
+ if (!gateState ||
50
+ typeof gateState !== "object" ||
51
+ typeof gateState.enabled !== "boolean" ||
52
+ typeof gateState.blocked !== "boolean") {
53
+ return false;
54
+ }
55
+
56
+ if (gateState.blocked === true) {
57
+ return true;
58
+ }
59
+
60
+ const reason = String(gateState.reason || "").trim().toLowerCase();
61
+ return Boolean(gateState.unlock) || REWARDED_NON_BLOCKING_REASONS.has(reason);
62
+ }
63
+
64
+ function buildApiPath(workspaceSlug = "", action = "", query = null) {
65
+ const normalizedWorkspaceSlug = normalizeWorkspaceSlug(workspaceSlug);
66
+ const pathname = `/api/w/${encodeURIComponent(normalizedWorkspaceSlug)}/rewarded/${action}`;
67
+ if (!(query instanceof URLSearchParams) || [...query.keys()].length < 1) {
68
+ return pathname;
69
+ }
70
+ return `${pathname}?${query.toString()}`;
71
+ }
72
+
73
+ function createRewardedRuntime({ launchReward } = {}) {
74
+ if (typeof launchReward !== "function") {
75
+ throw new TypeError("Rewarded runtime requires an explicit launchReward delivery function.");
76
+ }
77
+ const state = reactive(createInitialState());
78
+ let pendingResolve = null;
79
+ let activeGrantPromise = null;
80
+
81
+ function resetState() {
82
+ activeGrantPromise = null;
83
+ pendingResolve = null;
84
+ applyState(state, createInitialState());
85
+ }
86
+
87
+ function settle(result) {
88
+ const resolver = pendingResolve;
89
+ resetState();
90
+ if (typeof resolver === "function") {
91
+ resolver(result);
92
+ }
93
+ }
94
+
95
+ async function requestCurrent(input = {}) {
96
+ const params = new URLSearchParams({
97
+ gateKey: String(input.gateKey || "")
98
+ });
99
+ return rewardedHttpClient.request(
100
+ buildApiPath(input.workspaceSlug, "current", params),
101
+ {
102
+ method: "GET"
103
+ }
104
+ );
105
+ }
106
+
107
+ async function requestStart(input = {}) {
108
+ return rewardedHttpClient.request(
109
+ buildApiPath(input.workspaceSlug, "start"),
110
+ {
111
+ method: "POST",
112
+ body: {
113
+ gateKey: input.gateKey
114
+ }
115
+ }
116
+ );
117
+ }
118
+
119
+ async function requestGrant(input = {}) {
120
+ return rewardedHttpClient.request(
121
+ buildApiPath(input.workspaceSlug, "grant"),
122
+ {
123
+ method: "POST",
124
+ body: {
125
+ sessionId: input.sessionId
126
+ }
127
+ }
128
+ );
129
+ }
130
+
131
+ async function requestClose(input = {}) {
132
+ return rewardedHttpClient.request(
133
+ buildApiPath(input.workspaceSlug, "close"),
134
+ {
135
+ method: "POST",
136
+ body: {
137
+ sessionId: input.sessionId
138
+ }
139
+ }
140
+ );
141
+ }
142
+
143
+ async function requireUnlock(request = {}) {
144
+ const gateKey = String(request?.gateKey || "").trim();
145
+ const workspaceSlug = normalizeWorkspaceSlug(request?.workspaceSlug);
146
+
147
+ if (!gateKey) {
148
+ throw new Error("requireUnlock requires gateKey.");
149
+ }
150
+ if (!workspaceSlug) {
151
+ throw new Error("requireUnlock requires workspaceSlug.");
152
+ }
153
+
154
+ const gateState = await requestCurrent({
155
+ gateKey,
156
+ workspaceSlug
157
+ });
158
+ if (!isWellFormedGateState(gateState)) {
159
+ throw new Error("Rewarded gate returned an invalid state.");
160
+ }
161
+ const alreadyUnlocked = gateState?.blocked === false && gateState?.unlock;
162
+ if (!gateState?.enabled || !gateState?.blocked) {
163
+ return {
164
+ granted: Boolean(alreadyUnlocked),
165
+ state: gateState
166
+ };
167
+ }
168
+
169
+ if (pendingResolve) {
170
+ throw new Error("A Rewarded gate is already active.");
171
+ }
172
+
173
+ applyState(state, {
174
+ open: true,
175
+ phase: "prompt",
176
+ errorMessage: "",
177
+ gateState,
178
+ session: null,
179
+ request: {
180
+ gateKey,
181
+ workspaceSlug
182
+ }
183
+ });
184
+
185
+ return new Promise((resolve) => {
186
+ pendingResolve = resolve;
187
+ });
188
+ }
189
+
190
+ async function beginWatch() {
191
+ if (!state.request || state.phase !== "prompt") {
192
+ return;
193
+ }
194
+
195
+ applyState(state, {
196
+ ...state,
197
+ phase: "loading"
198
+ });
199
+
200
+ try {
201
+ const startState = await requestStart(state.request);
202
+ if (!startState?.session || !startState?.providerConfig) {
203
+ settle({
204
+ granted: false,
205
+ state: startState
206
+ });
207
+ return;
208
+ }
209
+
210
+ applyState(state, {
211
+ ...state,
212
+ gateState: startState,
213
+ session: startState.session,
214
+ errorMessage: ""
215
+ });
216
+
217
+ await launchReward({
218
+ providerConfig: startState.providerConfig,
219
+ onReady() {
220
+ applyState(state, {
221
+ ...state,
222
+ phase: "showing-ad"
223
+ });
224
+ },
225
+ async onGranted() {
226
+ activeGrantPromise = requestGrant({
227
+ workspaceSlug: state.request.workspaceSlug,
228
+ sessionId: state.session?.id
229
+ });
230
+ const grantResult = await activeGrantPromise;
231
+ applyState(state, {
232
+ ...state,
233
+ phase: "granted",
234
+ gateState: {
235
+ ...state.gateState,
236
+ ...grantResult,
237
+ blocked: false,
238
+ unlock: grantResult.unlock || null
239
+ }
240
+ });
241
+ },
242
+ async onClosed() {
243
+ if (activeGrantPromise) {
244
+ const grantResult = await activeGrantPromise;
245
+ settle({
246
+ granted: true,
247
+ state: {
248
+ ...state.gateState,
249
+ ...grantResult,
250
+ blocked: false,
251
+ unlock: grantResult.unlock || null
252
+ }
253
+ });
254
+ return;
255
+ }
256
+
257
+ const closeResult = await requestClose({
258
+ workspaceSlug: state.request.workspaceSlug,
259
+ sessionId: state.session?.id
260
+ });
261
+ settle({
262
+ granted: false,
263
+ state: {
264
+ ...state.gateState,
265
+ ...closeResult,
266
+ blocked: true
267
+ }
268
+ });
269
+ },
270
+ onUnavailable() {
271
+ applyState(state, {
272
+ ...state,
273
+ phase: "error",
274
+ errorMessage: "No rewarded ad was available right now. Please try again later."
275
+ });
276
+ }
277
+ });
278
+ } catch (error) {
279
+ applyState(state, {
280
+ ...state,
281
+ phase: "error",
282
+ errorMessage: error instanceof Error ? error.message : String(error)
283
+ });
284
+ }
285
+ }
286
+
287
+ async function cancelPrompt() {
288
+ if (!state.request) {
289
+ return;
290
+ }
291
+
292
+ if (state.session?.id) {
293
+ try {
294
+ await requestClose({
295
+ workspaceSlug: state.request.workspaceSlug,
296
+ sessionId: state.session.id
297
+ });
298
+ } catch {
299
+ // Preserve the original user intent even if cleanup fails.
300
+ }
301
+ }
302
+
303
+ settle({
304
+ granted: false,
305
+ state: state.gateState
306
+ });
307
+ }
308
+
309
+ async function dismissError() {
310
+ await cancelPrompt();
311
+ }
312
+
313
+ return Object.freeze({
314
+ state,
315
+ requireUnlock,
316
+ beginWatch,
317
+ cancelPrompt,
318
+ dismissError
319
+ });
320
+ }
321
+
322
+ export {
323
+ REWARDED_RUNTIME_INJECTION_KEY,
324
+ createRewardedRuntime
325
+ };
package/src/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from "./client/index.js";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};