mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-22 08:38:33 +02:00
feat: decaveat Phase 2 — subscription->tier data source + robust client signing
Caveat 2 (premium sub had no data source) -> RESOLVED: - src/services/subscriptionService.js: resolves a premium tier by npub. Two sources, both optional (absent => null => unchanged): PREMIUM_NPUBS operator allowlist (npub1/hex[:tier]) works TODAY with zero backend dependency, and an optional SMIRK_PREMIUM_STATUS_URL HTTP endpoint for full automation. Cached. - identityService.entitlementsForUser populates user.subscription_tier from it (by smirk_public_key) before the snapshot, so an active sub unlocks cosmetics. (The Smirk backend's /premium/status is self-only, so a by-npub service lookup is the one remaining backend addition for full automation — documented.) Caveat 1 (client signing was bare scaffold) -> HARDENED: - html/js/smirkChatSign.js now does the Nostr-scope-grant retry (sign first; on NOT_AUTHORIZED, getNostrPublicKey() once + retry), same as SmirkAuth — so a returning user signs with one approval instead of silently falling back. Still feature-detected + graceful. A real Smirk wallet click is the last confirm. Docs updated (MONETIZATION.md, CHAT_AND_NOSTR.md). Tests: +6 (allowlist hex/npub1, HTTP active/inactive+cache, identityService unlock, non-premium unchanged). Suite 57 suites / 413 tests green.
This commit is contained in:
@@ -46,7 +46,7 @@ The server signs every global message with one allowlisted bridge npub; the play
|
||||
everyone. `createBridgeSigner(NOSTR_BRIDGE_SK)` builds it; the npub must be write-allowlisted on the relay
|
||||
(see *Relay policy*).
|
||||
|
||||
### Phase 2 — per-player signing (built, needs browser verification)
|
||||
### Phase 2 — per-player signing (built; one real-wallet click to confirm)
|
||||
The **client** signs with the player's own Smirk npub and sends the finished event; the server verifies +
|
||||
relays it. On nostr it's genuinely *them*.
|
||||
|
||||
@@ -70,8 +70,12 @@ client server (chatHandler) relay
|
||||
- **`chatHandler.handleSignedChatMessage`** — rate-limit → resolve session npub → verify → escape → relay.
|
||||
- **`NostrChatProvider.relaySignedEvent`** — local delivery + `transport.publish(signedEvent)` (no
|
||||
re-signing) + echo-dedupe on the event id.
|
||||
- **`html/js/smirkChatSign.js`** — feature-detected client helper. Signs via `window.smirk.signNostrEvent`,
|
||||
falls back to the unsigned send when Smirk isn't connected, so wiring it into a send site is always safe.
|
||||
- **`html/js/smirkChatSign.js`** — feature-detected client helper. Signs via `window.smirk.signNostrEvent`
|
||||
with the Nostr-scope-grant retry (sign first; on `NOT_AUTHORIZED`, `getNostrPublicKey()` once and
|
||||
retry — same pattern as `SmirkAuth`), so a returning user gets a single approval. Falls back to the
|
||||
unsigned send when Smirk isn't connected, so wiring it into a send site is always safe. Loaded on
|
||||
`tavern.html`; the tavern send tries it first. (A real Smirk wallet click is the only step left to
|
||||
confirm end to end.)
|
||||
|
||||
## The tier model (two relays)
|
||||
|
||||
|
||||
+22
-9
@@ -42,23 +42,36 @@ any purchase unlocked every premium pack.
|
||||
tile/customization packs at/below it — **one subscription drives both chat perks and cosmetics** (see
|
||||
[CHAT_AND_NOSTR.md](CHAT_AND_NOSTR.md)).
|
||||
|
||||
Everything downstream — pack gating, the served catalog, the client unlock UI — already consumes `tier`, so
|
||||
"premium buys cosmetics" is a **data-source** change: populate `user.subscription_tier` from a subscription
|
||||
check (against the premium backend / npub) when the user is loaded. Absent ⇒ unchanged legacy behavior.
|
||||
Everything downstream — pack gating, the served catalog, the client unlock UI — already consumes `tier`.
|
||||
The data source is `src/services/subscriptionService.js` (`SubscriptionService.tierForNpub`), which
|
||||
`identityService.entitlementsForUser` calls to populate `user.subscription_tier` before the snapshot:
|
||||
|
||||
```
|
||||
active premium sub ──► user.subscription_tier ──► levelForUser ──► entitlement tier
|
||||
├─ relay: chat perks (badge, no-PoW, VIP)
|
||||
└─ catalog: tile / skin / customization unlocks
|
||||
active premium sub ──► SubscriptionService.tierForNpub(npub) ──► user.subscription_tier ──► levelForUser ──► tier
|
||||
├─ relay: chat perks (badge, no-PoW, VIP)
|
||||
└─ catalog: tile / skin / customization unlocks
|
||||
```
|
||||
|
||||
Two sources, checked in order (both optional — absent ⇒ tier null ⇒ unchanged legacy behavior):
|
||||
|
||||
| Env | Meaning |
|
||||
|-----|---------|
|
||||
| `PREMIUM_NPUBS` | operator allowlist `npub1…\|hex[:tier],…` — works **today**, zero backend dependency |
|
||||
| `PREMIUM_DEFAULT_TIER` | tier for allowlist entries without an explicit `:tier` (default `premium`) |
|
||||
| `SMIRK_PREMIUM_STATUS_URL` / `_KEY` | optional HTTP endpoint answering premium-by-npub `{active, tier?}`, for full automation |
|
||||
|
||||
> The Smirk backend's `/premium/status` is **self-only** (user-token auth), so the game can't look up
|
||||
> an arbitrary npub through it. Full automation needs a small backend addition — a service/by-npub
|
||||
> premium endpoint (or a webhook that sets the tier). Until then, `PREMIUM_NPUBS` is the working source.
|
||||
|
||||
## Files
|
||||
|
||||
```
|
||||
src/migrations/024_cosmetic_catalog.sql operator-owned catalog table
|
||||
src/services/catalogService.js load/cache + DEFAULT_CATALOG fallback
|
||||
src/multiplayer/entitlements.js snapshotForUser · levelForUser · TIER_OF · unlock rule
|
||||
src/network/identityService.js entitlementsForUser (loads catalog + grants)
|
||||
src/services/subscriptionService.js premium tier by npub (PREMIUM_NPUBS allowlist + HTTP seam)
|
||||
src/network/identityService.js entitlementsForUser (loads catalog + grants + sub tier)
|
||||
src/game/gameModeManager.js recordDirectEntryPurchase (direct entry = 1 credit)
|
||||
html/js/render/assetPacks.js client consumes the served catalog
|
||||
```
|
||||
@@ -67,5 +80,5 @@ html/js/render/assetPacks.js client consumes the served catalog
|
||||
|
||||
- Premium tiers: one flat "premium," or a ladder (supporter/premium/operator) where each rung unlocks
|
||||
progressively more cosmetics *and* chat perks? (`TIER_OF` already has the rungs.)
|
||||
- Where `subscription_tier` is sourced (sync a `users` column vs. live oracle query against the premium
|
||||
backend by npub).
|
||||
- Full-automation source: add a service/by-npub premium endpoint to the Smirk backend (then set
|
||||
`SMIRK_PREMIUM_STATUS_URL`) vs. a webhook that stamps the tier. `PREMIUM_NPUBS` covers it manually today.
|
||||
|
||||
Reference in New Issue
Block a user