@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.
- package/README.md +44 -0
- package/package.json +61 -0
- package/patterns/google-rewarded/PATTERN.md +69 -0
- package/patterns/google-rewarded/example/GoogleRewardedDeliveryProvider.js +12 -0
- package/patterns/google-rewarded/example/googlePublisherTag.js +142 -0
- package/src/client/components/RewardedGateHost.vue +184 -0
- package/src/client/composables/useRewardedRuntime.js +8 -0
- package/src/client/index.js +7 -0
- package/src/client/providers/RewardedClientProvider.js +60 -0
- package/src/client/runtime/rewardedRuntime.js +325 -0
- package/src/index.js +1 -0
- package/src/server/index.js +1 -0
- package/src/shared/index.js +1 -0
- package/test/runtime.test.js +600 -0
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# @jskit-ai/rewarded-web
|
|
2
|
+
|
|
3
|
+
Client workflow and gate UI for application-owned rewards. The package requires
|
|
4
|
+
`client.vue` and an explicit `client.rewarded-delivery` capability. Installing it
|
|
5
|
+
does not load an ad SDK or select a provider.
|
|
6
|
+
|
|
7
|
+
## Application use
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
const rewarded = useRewardedRuntime();
|
|
11
|
+
const result = await rewarded.requireUnlock({ gateKey: "bonus", workspaceSlug });
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The workflow opens a prompt before delivery. It handles existing unlocks, grants,
|
|
15
|
+
closed sessions and unavailable delivery through the app's `/rewarded/*` routes.
|
|
16
|
+
The delivery capability supplies `launchReward({ providerConfig, onReady,
|
|
17
|
+
onGranted, onClosed, onUnavailable })`, returning a promise for its lifecycle.
|
|
18
|
+
Provider configuration is opaque to the workflow. The application authorizes
|
|
19
|
+
server grants; browser callbacks alone do not prove provider viewing.
|
|
20
|
+
|
|
21
|
+
The existing gate is app-surface-only. The installed provider mounts its host
|
|
22
|
+
and supplies `client.rewarded`; direct callers use `createRewardedRuntime({
|
|
23
|
+
launchReward })`. Missing delivery fails rather than selecting Google.
|
|
24
|
+
|
|
25
|
+
## Google delivery pattern
|
|
26
|
+
|
|
27
|
+
See [Google rewarded delivery](patterns/google-rewarded/PATTERN.md). Copy the
|
|
28
|
+
adapter and provider into the application and register its provider alongside
|
|
29
|
+
`RewardedClientProvider`. The example handles Publisher Tag script loading,
|
|
30
|
+
rewarded events and slot cleanup. It accepts provider `google-publisher-tag` and
|
|
31
|
+
uses the configured placement as the Google ad unit path.
|
|
32
|
+
|
|
33
|
+
## V0 migration
|
|
34
|
+
|
|
35
|
+
Replace old package imports, `GoogleRewarded*` symbols and `google-rewarded.*`
|
|
36
|
+
capabilities with the generic names. Replace `useGoogleRewardedRuntime()` with
|
|
37
|
+
`useRewardedRuntime()` and supply delivery explicitly for direct construction.
|
|
38
|
+
There is no compatibility fallback. The core README describes table, route and
|
|
39
|
+
configuration migration. Install `@jskit-ai/rewarded-core` for server workflow
|
|
40
|
+
ownership; the Google example remains application source.
|
|
41
|
+
|
|
42
|
+
The package manifest lists application-owned inputs in both `capabilities.requires`
|
|
43
|
+
and `capabilities.applicationRequires`. The latter identifies who supplies them;
|
|
44
|
+
it does not make the runtime dependency optional or provide a default.
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jskit-ai/rewarded-web",
|
|
3
|
+
"version": "0.1.120",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./src/index.js",
|
|
7
|
+
"./client": "./src/client/index.js",
|
|
8
|
+
"./server": "./src/server/index.js",
|
|
9
|
+
"./shared": "./src/shared/index.js"
|
|
10
|
+
},
|
|
11
|
+
"description": "Rewarded gate client workflow with explicitly composed application delivery.",
|
|
12
|
+
"jskit": {
|
|
13
|
+
"kind": "runtime",
|
|
14
|
+
"capabilities": {
|
|
15
|
+
"provides": [
|
|
16
|
+
"rewarded.web"
|
|
17
|
+
],
|
|
18
|
+
"requires": [
|
|
19
|
+
"client.vue",
|
|
20
|
+
"client.rewarded-delivery"
|
|
21
|
+
],
|
|
22
|
+
"applicationRequires": [
|
|
23
|
+
"client.rewarded-delivery"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"runtime": {
|
|
27
|
+
"server": {
|
|
28
|
+
"providers": []
|
|
29
|
+
},
|
|
30
|
+
"client": {
|
|
31
|
+
"providers": [
|
|
32
|
+
{
|
|
33
|
+
"entrypoint": "src/client/providers/RewardedClientProvider.js",
|
|
34
|
+
"export": "RewardedClientProvider"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"metadata": {
|
|
40
|
+
"apiSummary": {
|
|
41
|
+
"surfaces": [
|
|
42
|
+
{
|
|
43
|
+
"subpath": "./client",
|
|
44
|
+
"summary": "Exports the Rewarded client provider, runtime composable, and fullscreen gate host."
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
"client": {
|
|
49
|
+
"singleton": true
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@jskit-ai/rewarded-core": "0.1.120"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"@jskit-ai/http-runtime": "0.1.182",
|
|
58
|
+
"@jskit-ai/kernel": "0.1.184",
|
|
59
|
+
"@jskit-ai/shell-web": "0.1.188"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: rewards/google-rewarded
|
|
3
|
+
title: Google rewarded delivery
|
|
4
|
+
summary: Compose application-owned Google Publisher Tag delivery with the generic rewarded workflow.
|
|
5
|
+
keywords: rewarded, google, publisher tag, ads, unlock
|
|
6
|
+
requires: @jskit-ai/rewarded-web
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Google rewarded delivery
|
|
10
|
+
|
|
11
|
+
## Use when
|
|
12
|
+
|
|
13
|
+
The application owner has selected Google rewarded ads and configured its
|
|
14
|
+
publisher account and ad unit.
|
|
15
|
+
|
|
16
|
+
## Do not use when
|
|
17
|
+
|
|
18
|
+
The application needs a Google Ads API connection, application login, or an
|
|
19
|
+
independent server proof of ad viewing.
|
|
20
|
+
|
|
21
|
+
## Product decisions
|
|
22
|
+
|
|
23
|
+
Choose the rewarded feature, user eligibility, publisher account, placement,
|
|
24
|
+
consent experience and server grant policy. The app owns these decisions.
|
|
25
|
+
|
|
26
|
+
## Invariants
|
|
27
|
+
|
|
28
|
+
- Generic workflow code does not choose a delivery provider.
|
|
29
|
+
- The app explicitly provides `client.rewarded-delivery`.
|
|
30
|
+
- Browser reward events are not independent server verification of viewing.
|
|
31
|
+
- The adapter removes listeners and slots when settled.
|
|
32
|
+
|
|
33
|
+
## Framework APIs
|
|
34
|
+
|
|
35
|
+
Register `GoogleRewardedDeliveryProvider` in ordinary application client
|
|
36
|
+
capability composition beside `RewardedClientProvider` from
|
|
37
|
+
`@jskit-ai/rewarded-web/client`. The example supplies
|
|
38
|
+
`client.rewarded-delivery`; the generic UI supplies `client.rewarded`.
|
|
39
|
+
Use `useRewardedRuntime().requireUnlock({ gateKey, workspaceSlug })` for gates.
|
|
40
|
+
Separately supply server capability `rewarded.grant-policy` with
|
|
41
|
+
`authorizeGrant({ session, context, trx })`; only `true` permits a grant.
|
|
42
|
+
This delivery example does not provide or choose the server policy.
|
|
43
|
+
|
|
44
|
+
## Example files
|
|
45
|
+
|
|
46
|
+
Copy `example/googlePublisherTag.js` and
|
|
47
|
+
`example/GoogleRewardedDeliveryProvider.js` into application source. The adapter
|
|
48
|
+
loads Publisher Tag after the user starts the reward flow and maps
|
|
49
|
+
ready/granted/closed events to workflow callbacks.
|
|
50
|
+
|
|
51
|
+
## Variation points
|
|
52
|
+
|
|
53
|
+
Configure the app's reward provider row with `provider: "google-publisher-tag"`
|
|
54
|
+
and `placement` equal to its ad unit path. Choose gate limits and duration through
|
|
55
|
+
the core workflow's rule configuration. Use a different delivery provider for
|
|
56
|
+
other services; do not add implicit fallback inside the generic runtime.
|
|
57
|
+
|
|
58
|
+
## Verification
|
|
59
|
+
|
|
60
|
+
Use controlled SDK events to verify grant, close, unavailable slot and cleanup.
|
|
61
|
+
Test server authorization and isolation with the application's own user context.
|
|
62
|
+
Check duplicate grant and expired unlock behavior before enabling the feature.
|
|
63
|
+
|
|
64
|
+
## Avoid
|
|
65
|
+
|
|
66
|
+
- claiming browser events prove provider viewing to the server
|
|
67
|
+
- enabling Google delivery without an explicit application choice
|
|
68
|
+
- adding API credentials or provider registrations to generic workflow code
|
|
69
|
+
- treating an Ads API connector as rewarded browser delivery
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineProvider } from "@jskit-ai/kernel/shared/capabilities";
|
|
2
|
+
import { launchGoogleRewardedAd } from "./googlePublisherTag.js";
|
|
3
|
+
|
|
4
|
+
const GoogleRewardedDeliveryProvider = defineProvider({
|
|
5
|
+
id: "app.rewarded.google",
|
|
6
|
+
provides: { delivery: "client.rewarded-delivery" },
|
|
7
|
+
setup() {
|
|
8
|
+
return { delivery: { launchReward: launchGoogleRewardedAd } };
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export { GoogleRewardedDeliveryProvider };
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
let gptLoadPromise = null;
|
|
2
|
+
let gptServicesEnabled = false;
|
|
3
|
+
|
|
4
|
+
async function ensureGooglePublisherTagLoaded() {
|
|
5
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
6
|
+
throw new Error("Rewarded runtime requires a browser environment.");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (window.googletag?.apiReady === true) {
|
|
10
|
+
return window.googletag;
|
|
11
|
+
}
|
|
12
|
+
if (gptLoadPromise) {
|
|
13
|
+
return gptLoadPromise;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
window.googletag ||= { cmd: [] };
|
|
17
|
+
gptLoadPromise = new Promise((resolve, reject) => {
|
|
18
|
+
const existingScript = document.querySelector('script[data-jskit-rewarded-gpt="true"]');
|
|
19
|
+
if (existingScript) {
|
|
20
|
+
existingScript.addEventListener("load", () => resolve(window.googletag), { once: true });
|
|
21
|
+
existingScript.addEventListener("error", () => reject(new Error("Failed to load Google Publisher Tag.")), { once: true });
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const script = document.createElement("script");
|
|
26
|
+
script.async = true;
|
|
27
|
+
script.src = "https://securepubads.g.doubleclick.net/tag/js/gpt.js";
|
|
28
|
+
script.dataset.jskitRewardedGpt = "true";
|
|
29
|
+
script.addEventListener("load", () => resolve(window.googletag), { once: true });
|
|
30
|
+
script.addEventListener("error", () => reject(new Error("Failed to load Google Publisher Tag.")), { once: true });
|
|
31
|
+
document.head.appendChild(script);
|
|
32
|
+
}).catch((error) => {
|
|
33
|
+
gptLoadPromise = null;
|
|
34
|
+
throw error;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return gptLoadPromise;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function launchGoogleRewardedAd({
|
|
41
|
+
providerConfig,
|
|
42
|
+
onReady = null,
|
|
43
|
+
onGranted = null,
|
|
44
|
+
onClosed = null,
|
|
45
|
+
onUnavailable = null
|
|
46
|
+
} = {}) {
|
|
47
|
+
if (providerConfig?.provider !== "google-publisher-tag") {
|
|
48
|
+
throw new TypeError("Google delivery requires provider google-publisher-tag.");
|
|
49
|
+
}
|
|
50
|
+
const placement = String(providerConfig?.placement || "").trim();
|
|
51
|
+
if (!placement) throw new TypeError("Rewarded delivery requires placement.");
|
|
52
|
+
return ensureGooglePublisherTagLoaded().then((googletag) =>
|
|
53
|
+
new Promise((resolve, reject) => {
|
|
54
|
+
googletag.cmd.push(() => {
|
|
55
|
+
const pubads = googletag.pubads();
|
|
56
|
+
const rewardedFormat = googletag?.enums?.OutOfPageFormat?.REWARDED;
|
|
57
|
+
if (!rewardedFormat) {
|
|
58
|
+
reject(new Error("Rewarded ads are not supported by this GPT build."));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const slot = googletag.defineOutOfPageSlot(placement, rewardedFormat);
|
|
63
|
+
if (!slot) {
|
|
64
|
+
Promise.resolve(onUnavailable?.()).finally(() => {
|
|
65
|
+
reject(new Error("Rewarded ads are not available on this page."));
|
|
66
|
+
});
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let readySeen = false;
|
|
71
|
+
let settled = false;
|
|
72
|
+
|
|
73
|
+
const cleanup = () => {
|
|
74
|
+
if (typeof pubads.removeEventListener === "function") {
|
|
75
|
+
pubads.removeEventListener("rewardedSlotReady", handleReady);
|
|
76
|
+
pubads.removeEventListener("rewardedSlotGranted", handleGranted);
|
|
77
|
+
pubads.removeEventListener("rewardedSlotClosed", handleClosed);
|
|
78
|
+
}
|
|
79
|
+
if (typeof googletag.destroySlots === "function") {
|
|
80
|
+
googletag.destroySlots([slot]);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const handleReady = async (event) => {
|
|
85
|
+
if (event?.slot !== slot || settled) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
readySeen = true;
|
|
89
|
+
await Promise.resolve(onReady?.());
|
|
90
|
+
if (typeof event.makeRewardedVisible === "function") {
|
|
91
|
+
event.makeRewardedVisible();
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const handleGranted = async (event) => {
|
|
96
|
+
if (event?.slot !== slot || settled) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
await Promise.resolve(onGranted?.());
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const handleClosed = async (event) => {
|
|
103
|
+
if (event?.slot !== slot || settled) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
settled = true;
|
|
107
|
+
cleanup();
|
|
108
|
+
try {
|
|
109
|
+
await Promise.resolve(onClosed?.());
|
|
110
|
+
resolve();
|
|
111
|
+
} catch (error) {
|
|
112
|
+
reject(error);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
pubads.addEventListener("rewardedSlotReady", handleReady);
|
|
117
|
+
pubads.addEventListener("rewardedSlotGranted", handleGranted);
|
|
118
|
+
pubads.addEventListener("rewardedSlotClosed", handleClosed);
|
|
119
|
+
slot.addService(pubads);
|
|
120
|
+
|
|
121
|
+
if (!gptServicesEnabled) {
|
|
122
|
+
googletag.enableServices();
|
|
123
|
+
gptServicesEnabled = true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
googletag.display(slot);
|
|
127
|
+
window.setTimeout(() => {
|
|
128
|
+
if (settled || readySeen) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
settled = true;
|
|
132
|
+
cleanup();
|
|
133
|
+
Promise.resolve(onUnavailable?.()).finally(() => {
|
|
134
|
+
reject(new Error("No rewarded ad was available."));
|
|
135
|
+
});
|
|
136
|
+
}, 10000);
|
|
137
|
+
});
|
|
138
|
+
})
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export { launchGoogleRewardedAd };
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { useRewardedRuntime } from "../composables/useRewardedRuntime.js";
|
|
4
|
+
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
runtime: {
|
|
7
|
+
type: Object,
|
|
8
|
+
required: false,
|
|
9
|
+
default: null
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const runtime = computed(() => props.runtime || useRewardedRuntime());
|
|
14
|
+
const state = computed(() => runtime.value?.state || null);
|
|
15
|
+
const isVisible = computed(() => {
|
|
16
|
+
const currentState = state.value;
|
|
17
|
+
return currentState?.open === true && currentState?.phase !== "showing-ad";
|
|
18
|
+
});
|
|
19
|
+
const titleText = computed(() => state.value?.gateState?.rule?.title || "Watch an ad to continue");
|
|
20
|
+
const descriptionText = computed(() =>
|
|
21
|
+
state.value?.gateState?.rule?.description ||
|
|
22
|
+
"Watch a rewarded ad to unlock this action."
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
function handleWatchClick() {
|
|
26
|
+
runtime.value?.beginWatch?.();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function handleCancelClick() {
|
|
30
|
+
runtime.value?.cancelPrompt?.();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function handleDismissError() {
|
|
34
|
+
runtime.value?.dismissError?.();
|
|
35
|
+
}
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<template>
|
|
39
|
+
<Teleport to="body">
|
|
40
|
+
<div v-if="isVisible" class="rewarded-gate">
|
|
41
|
+
<div class="rewarded-gate__scrim" />
|
|
42
|
+
<div class="rewarded-gate__panel">
|
|
43
|
+
<div class="rewarded-gate__eyebrow">Rewarded unlock</div>
|
|
44
|
+
<h2 class="rewarded-gate__title">{{ titleText }}</h2>
|
|
45
|
+
<p class="rewarded-gate__description">{{ descriptionText }}</p>
|
|
46
|
+
|
|
47
|
+
<p
|
|
48
|
+
v-if="state?.phase === 'loading'"
|
|
49
|
+
class="rewarded-gate__status"
|
|
50
|
+
>
|
|
51
|
+
Loading Rewarded ad…
|
|
52
|
+
</p>
|
|
53
|
+
|
|
54
|
+
<p
|
|
55
|
+
v-else-if="state?.phase === 'error'"
|
|
56
|
+
class="rewarded-gate__status rewarded-gate__status--error"
|
|
57
|
+
>
|
|
58
|
+
{{ state?.errorMessage || "Unable to start the rewarded ad." }}
|
|
59
|
+
</p>
|
|
60
|
+
|
|
61
|
+
<div class="rewarded-gate__actions">
|
|
62
|
+
<button
|
|
63
|
+
v-if="state?.phase === 'prompt'"
|
|
64
|
+
type="button"
|
|
65
|
+
class="rewarded-gate__button rewarded-gate__button--primary"
|
|
66
|
+
@click="handleWatchClick"
|
|
67
|
+
>
|
|
68
|
+
Watch ad
|
|
69
|
+
</button>
|
|
70
|
+
|
|
71
|
+
<button
|
|
72
|
+
v-if="state?.phase === 'prompt'"
|
|
73
|
+
type="button"
|
|
74
|
+
class="rewarded-gate__button"
|
|
75
|
+
@click="handleCancelClick"
|
|
76
|
+
>
|
|
77
|
+
Not now
|
|
78
|
+
</button>
|
|
79
|
+
|
|
80
|
+
<button
|
|
81
|
+
v-if="state?.phase === 'error'"
|
|
82
|
+
type="button"
|
|
83
|
+
class="rewarded-gate__button rewarded-gate__button--primary"
|
|
84
|
+
@click="handleDismissError"
|
|
85
|
+
>
|
|
86
|
+
Close
|
|
87
|
+
</button>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</Teleport>
|
|
92
|
+
</template>
|
|
93
|
+
|
|
94
|
+
<style scoped>
|
|
95
|
+
.rewarded-gate {
|
|
96
|
+
position: fixed;
|
|
97
|
+
inset: 0;
|
|
98
|
+
z-index: 2147483000;
|
|
99
|
+
display: flex;
|
|
100
|
+
align-items: center;
|
|
101
|
+
justify-content: center;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.rewarded-gate__scrim {
|
|
105
|
+
position: absolute;
|
|
106
|
+
inset: 0;
|
|
107
|
+
background:
|
|
108
|
+
linear-gradient(180deg, rgba(15, 23, 42, 0.82), rgba(2, 6, 23, 0.94));
|
|
109
|
+
backdrop-filter: blur(4px);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.rewarded-gate__panel {
|
|
113
|
+
position: relative;
|
|
114
|
+
width: min(32rem, calc(100vw - 2rem));
|
|
115
|
+
border-radius: 1.5rem;
|
|
116
|
+
padding: 1.5rem;
|
|
117
|
+
background: #fffaf0;
|
|
118
|
+
color: #201510;
|
|
119
|
+
box-shadow: 0 2rem 5rem rgba(15, 23, 42, 0.35);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.rewarded-gate__eyebrow {
|
|
123
|
+
font-size: 0.75rem;
|
|
124
|
+
font-weight: 700;
|
|
125
|
+
letter-spacing: 0.12em;
|
|
126
|
+
text-transform: uppercase;
|
|
127
|
+
color: #9a3412;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.rewarded-gate__title {
|
|
131
|
+
margin: 0.5rem 0 0;
|
|
132
|
+
font-size: 1.5rem;
|
|
133
|
+
line-height: 1.2;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.rewarded-gate__description {
|
|
137
|
+
margin: 0.75rem 0 0;
|
|
138
|
+
line-height: 1.5;
|
|
139
|
+
color: #5b4636;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.rewarded-gate__status {
|
|
143
|
+
margin: 1rem 0 0;
|
|
144
|
+
font-size: 0.95rem;
|
|
145
|
+
color: #854d0e;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.rewarded-gate__status--error {
|
|
149
|
+
color: #991b1b;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.rewarded-gate__actions {
|
|
153
|
+
display: flex;
|
|
154
|
+
gap: 0.75rem;
|
|
155
|
+
margin-top: 1.25rem;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.rewarded-gate__button {
|
|
159
|
+
appearance: none;
|
|
160
|
+
border: 1px solid #d6d3d1;
|
|
161
|
+
border-radius: 999px;
|
|
162
|
+
padding: 0.8rem 1.2rem;
|
|
163
|
+
background: #fff;
|
|
164
|
+
color: inherit;
|
|
165
|
+
font: inherit;
|
|
166
|
+
cursor: pointer;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.rewarded-gate__button--primary {
|
|
170
|
+
border-color: transparent;
|
|
171
|
+
background: #ea580c;
|
|
172
|
+
color: #fff;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@media (max-width: 640px) {
|
|
176
|
+
.rewarded-gate__actions {
|
|
177
|
+
flex-direction: column;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.rewarded-gate__button {
|
|
181
|
+
width: 100%;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
</style>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { RewardedClientProvider } from "./providers/RewardedClientProvider.js";
|
|
2
|
+
export { default as RewardedGateHost } from "./components/RewardedGateHost.vue";
|
|
3
|
+
export {
|
|
4
|
+
REWARDED_RUNTIME_INJECTION_KEY,
|
|
5
|
+
createRewardedRuntime
|
|
6
|
+
} from "./runtime/rewardedRuntime.js";
|
|
7
|
+
export { useRewardedRuntime } from "./composables/useRewardedRuntime.js";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { defineProvider } from "@jskit-ai/kernel/shared/capabilities";
|
|
2
|
+
import { h, render } from "vue";
|
|
3
|
+
import RewardedGateHost from "../components/RewardedGateHost.vue";
|
|
4
|
+
import {
|
|
5
|
+
REWARDED_RUNTIME_INJECTION_KEY,
|
|
6
|
+
createRewardedRuntime
|
|
7
|
+
} from "../runtime/rewardedRuntime.js";
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const mountedHosts = new WeakMap();
|
|
11
|
+
|
|
12
|
+
const RewardedClientProvider = defineProvider({
|
|
13
|
+
id: "rewarded.web.client",
|
|
14
|
+
requires: {
|
|
15
|
+
vueApp: "client.vue",
|
|
16
|
+
delivery: "client.rewarded-delivery"
|
|
17
|
+
},
|
|
18
|
+
provides: {
|
|
19
|
+
rewarded: "client.rewarded"
|
|
20
|
+
},
|
|
21
|
+
setup({ delivery }) {
|
|
22
|
+
return {
|
|
23
|
+
rewarded: createRewardedRuntime({ launchReward: delivery.launchReward })
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
async boot({ vueApp }, { outputs }) {
|
|
27
|
+
if (typeof document === "undefined") {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const runtime = outputs.rewarded;
|
|
32
|
+
|
|
33
|
+
if (vueApp && typeof vueApp.provide === "function") {
|
|
34
|
+
vueApp.provide(REWARDED_RUNTIME_INJECTION_KEY, runtime);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const hostContainer = document.createElement("div");
|
|
38
|
+
hostContainer.dataset.jskitRewardedHost = "true";
|
|
39
|
+
document.body.appendChild(hostContainer);
|
|
40
|
+
|
|
41
|
+
const vnode = h(RewardedGateHost, {
|
|
42
|
+
runtime
|
|
43
|
+
});
|
|
44
|
+
vnode.appContext = vueApp?._context || null;
|
|
45
|
+
render(vnode, hostContainer);
|
|
46
|
+
mountedHosts.set(runtime, hostContainer);
|
|
47
|
+
},
|
|
48
|
+
shutdown(_dependencies, { outputs }) {
|
|
49
|
+
const hostContainer = mountedHosts.get(outputs.rewarded);
|
|
50
|
+
if (!hostContainer) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
render(null, hostContainer);
|
|
55
|
+
hostContainer.remove();
|
|
56
|
+
mountedHosts.delete(outputs.rewarded);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export { RewardedClientProvider };
|