Repository navigation
Plugin Ecosystem
dopaminejs-plugin-ecosystem 2.0.0. Needs dopaminejs 2.3.0 or newer.
npm install dopaminejs-plugin-ecosystemFour exports: BattlePassPlugin, LeaderboardPlugin, WebhookIntegration, RewardMiddleware.
Version 2.0.0 changed the webhook format. Coming from 1.x, read Migration first.
Both plugins listen on the kernel bus, so the reward system must publish there.
import { Game, RewardSystem, DataService } from 'dopaminejs';
import { BattlePassPlugin, LeaderboardPlugin } from 'dopaminejs-plugin-ecosystem';
const game = new Game();
const rewards = new RewardSystem(new DataService(), { events: game.kernel.events });
game.kernel.plugins.use(BattlePassPlugin);
await rewards.init();Without events (or kernel) in the reward config, the plugins hear nothing.
Four tiers by total XP.
| Tier level | XP | Reward |
|---|---|---|
| 1 | 0 | Bronze Badge |
| 5 | 500 | Silver Badge |
| 10 | 1500 | Gold Badge |
| 20 | 5000 | Platinum Badge |
When xp_gained puts the total at or past a tier, the plugin emits battlepass:tier-unlocked with the tier object, on the kernel bus and as a CustomEvent on window.
game.kernel.events.on('battlepass:tier-unlocked', (tier) => {
console.log(tier.level, tier.reward);
});
window.addEventListener('battlepass:tier-unlocked', (event) => {
console.log(event.detail.reward);
});Which tiers were announced is kept in memory only. After a reload, every tier at or below the player's XP fires again on the first xp_gained. Store what you granted and ignore repeats.
Posts level ups and achievements to your endpoint.
LeaderboardPlugin.configure({
webhookUrl: 'https://example.com/hooks/dopamine',
secret: 'shared-secret',
playerId: () => currentUser.id
});
game.kernel.plugins.use(LeaderboardPlugin);configure must run before use. Without a webhookUrl the plugin warns and sends nothing.
playerId is a string or a function. Left out, the plugin generates an id and keeps it in localStorage under dopamine_player_id.
| Event | data |
|---|---|
level_up |
{ playerId, level, xp } |
achievement |
{ playerId, achievementId, timestamp } |
The sender the leaderboard uses. You can use it directly.
import { WebhookIntegration } from 'dopaminejs-plugin-ecosystem';
const webhook = new WebhookIntegration({
webhookUrl: 'https://example.com/hooks/dopamine',
secret: 'shared-secret',
maxQueue: 100
});
await webhook.send('purchase', { sku: 'gem-pack' });The request is a POST with Content-Type: application/json and this body:
{ "event": "purchase", "data": { "sku": "gem-pack" }, "timestamp": 1790000000000 }- Requests go out one at a time, in order.
- Past
maxQueuewaiting requests, the oldest is dropped. - A failed request is logged. It is not retried.
-
enabled: falseturns sending off.
With a secret, each request carries:
X-Dopamine-Signature: sha256=<hex>
The value is HMAC-SHA256 of the exact request body, keyed with the secret. Verify it against the raw body before parsing.
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody, header, secret) {
const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(header ?? '');
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}Where crypto.subtle is missing (a page served over plain HTTP, for example) the request is sent unsigned, with one warning.
A secret in browser code can be read by the player. The signature shows the request came from a copy of your app. It does not show the numbers are true. Treat the payload as a claim, and check anything that matters against your own records. See Security.
A hook list you run yourself. It is not connected to RewardSystem.
import { RewardMiddleware } from 'dopaminejs-plugin-ecosystem';
const middleware = new RewardMiddleware();
middleware.use('beforeXP', (data) => ({ amount: data.amount * 2 }));
middleware.use('afterXP', (data) => analytics.track('xp', data));
const { amount } = await middleware.executeBefore('XP', { amount: 50 });
await rewards.addXP(amount);
await middleware.executeAfter('XP', { amount });Hook names: beforeXP, afterXP, beforeLevelUp, afterLevelUp, beforeAchievement, afterAchievement.
- A
beforehook may return an object. It is merged over the data and passed on. - A hook that throws is logged and skipped.
- An unknown hook name logs a warning.
BattlePassPlugin and LeaderboardPlugin are module-level objects. One page, one game. Two kernels using the same plugin object share its state.
Start
Progression
Engine
Packages
Reference