mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-20 07:38:34 +02:00
feat(chat): per-player nostr identity (Phase 2) + premium subscription unlocks cosmetic tiers
Phase 2 — a signed-in player can post global chat under their OWN Smirk npub instead of the shared bridge account: - src/utils/verifyChatEvent.js: verify a client-signed chat event with the same defenses as verifyNip98Event (rebuild clean event, recompute id, verify schnorr, freshness) PLUS the impersonation guard — the event's pubkey MUST equal the session's authenticated npub (users.smirk_public_key from NIP-98 login). - ChatProvider.relaySignedEvent (base: local delivery only) + NostrChatProvider override (local delivery + publish the PRE-SIGNED event to the relays, no re-signing, echo-deduped) so it carries the player's npub. - chatHandler.handleSignedChatMessage: rate-limit + resolve the session npub + verify + escape + relay. socketHandlers wires the event. Serves lobby + tavern (both render the global broadcast). - html/js/smirkChatSign.js + tavern send wire: feature-detected client helper — signs via window.smirk.signNostrEvent, falls back to the unsigned send when Smirk isn't connected, so it's inert (and safe) until a page has a wallet. Premium -> cosmetics (the P1 catalog already had the hook): - entitlements.levelForUser now takes the HIGHER of premium_level and an active-subscription tier, so a wowne.ro premium sub sets the entitlement tier and unlocks tile/customization packs at/below it — one sub, chat perks + cosmetics. Buying credits still never joins the premium ladder. Behavior-preserving: no subscription_tier => unchanged; chat_signed only fires when a client opts in. The client signing path needs browser verification against a connected Smirk wallet (like the NIP-98 login). Tests: +13 (verify incl. impersonation/tamper/channel/freshness, relay+echo-dedupe, sub-tier unlock). Suite 56 suites / 407 tests green.
This commit is contained in:
@@ -70,10 +70,17 @@ function normalizePackGrants(grants = [], catalog = DEFAULT_CATALOG) {
|
||||
}
|
||||
|
||||
// Only a REAL premium tier (supporter/premium/operator) counts as a level; buying credits does not.
|
||||
// The tier is the HIGHEST of the stored premium_level and any active-subscription tier: a live
|
||||
// wowne.ro premium sub sets subscription_tier (e.g. 'premium') and thereby unlocks the cosmetic
|
||||
// packs at/below that tier — one subscription drives both chat perks and tile/customization unlocks.
|
||||
// (The subscription_tier field is populated by the user loader from a subscription check; absent =
|
||||
// unchanged legacy behavior.)
|
||||
function levelForUser(user = {}) {
|
||||
const raw = String(user.premium_level || user.premiumLevel || '').trim().toLowerCase();
|
||||
if (raw && tierForLevel(raw) > 0) return raw;
|
||||
return 'free';
|
||||
const candidates = [user.premium_level, user.premiumLevel, user.subscription_tier, user.subscriptionTier]
|
||||
.map(v => String(v || '').trim().toLowerCase())
|
||||
.filter(l => tierForLevel(l) > 0);
|
||||
if (!candidates.length) return 'free';
|
||||
return candidates.reduce((a, b) => (tierForLevel(b) > tierForLevel(a) ? b : a));
|
||||
}
|
||||
|
||||
// A pack is unlocked for a user if ANY of: it's free, an explicit grant, the user's lifetime
|
||||
|
||||
Reference in New Issue
Block a user