mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-18 22:58:32 +02:00
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.
32 lines
1.6 KiB
JavaScript
32 lines
1.6 KiB
JavaScript
(function (root) {
|
|
'use strict';
|
|
// Per-player nostr identity (Phase 2). Sign a global-chat message with the player's OWN Smirk
|
|
// npub (window.smirk.signNostrEvent) and emit it as `chat_signed`; the server verifies it's
|
|
// signed by this session's authenticated npub, then relays it to nostr under that npub.
|
|
//
|
|
// Returns a Promise<boolean>: true if it signed + emitted, false if Smirk signing is
|
|
// unavailable or declined — in which case the caller falls back to the normal unsigned send.
|
|
// Feature-detected end to end, so it's a no-op (always false) until the page has a connected
|
|
// Smirk wallet; wiring it into a send site is therefore always safe.
|
|
function smirkChatSign(socket, text, tag) {
|
|
try {
|
|
var smirk = root.smirk;
|
|
if (!socket || !smirk || typeof smirk.signNostrEvent !== 'function') return Promise.resolve(false);
|
|
var tmpl = {
|
|
kind: 1,
|
|
created_at: Math.floor(Date.now() / 1000),
|
|
tags: [['t', tag || 'wowngeon-global']],
|
|
content: String(text)
|
|
};
|
|
return Promise.resolve(smirk.signNostrEvent(tmpl)).then(function (signed) {
|
|
if (!signed || !signed.id || !signed.sig || !signed.pubkey) return false;
|
|
socket.emit('chat_signed', { event: signed });
|
|
return true;
|
|
}).catch(function () { return false; });
|
|
} catch (e) {
|
|
return Promise.resolve(false);
|
|
}
|
|
}
|
|
root.smirkChatSign = smirkChatSign;
|
|
})(typeof window !== 'undefined' ? window : this);
|