Skip to content

Chat Store

BarryThePirate edited this page Apr 12, 2026 · 1 revision

Chat Store

Direct access to the site's Zustand chat store via React fiber tree traversal. Lets you read, modify, or remove chat messages in React state — before they render to the DOM.

This is the recommended way to filter or modify chat messages. For receiving messages with normalised data and TTS/SFX deduplication, use Chat Messages instead.

Realm requirement

This module must run in the page's JavaScript realm. It walks React's internal fiber tree, which is only accessible from the same realm React is running in.

Environment Works directly?
Tampermonkey/Greasemonkey ✅ Yes
Page-injected <script> tag ✅ Yes
Browser extension content script ❌ No — content scripts run in an isolated realm

For browser extensions, you need to inject a separate page-realm script that imports this module. The extension's content script can communicate with it via window.postMessage. The Fishtank Live Extended extension uses this pattern in chat-filter.js.

Usage

import { chat } from 'ftl-ext-sdk';

// Wait for the chat store to become available (it loads after the chat
// component mounts, which is a few seconds after page load)
await chat.store.waitForStore();

// Subscribe to new messages
chat.store.onMessage((msg) => {
  console.log(`${msg.user.displayName}: ${msg.message}`);
});

// Read messages directly
const messages = chat.store.getMessages();

// Remove a single message by ID
chat.store.removeMessage('some-message-id');

// Remove messages matching a predicate
const removed = chat.store.removeWhere((msg) => {
  return msg.message?.includes('spam');
});
console.log(`Removed ${removed} spam messages`);

// Replace the messages array entirely
chat.store.setMessages(filteredMessages);

// Subscribe to all store changes (not just new messages)
const unsub = chat.store.subscribe((state) => {
  console.log('Store changed, current room:', state.chatRoom);
});

// Stop subscribing
unsub();

// Status checks
chat.store.isReady();        // boolean — has the store been located?
chat.store.getStore();       // raw Zustand store reference
chat.store.reset();          // clear cached store reference

Store shape

The chat store is a Zustand store with (among other things):

Key Type Description
chatMessages Array All chat messages currently in state
setChatMessages Function Replace the messages array
chatRoom string Current room ('Global', 'Season Pass', etc.)
blockedUsers Array User IDs the local user has blocked
wordFilters Array The local user's word filters

The full store has ~28 keys. Use getStore().getState() to inspect.

Message shape

Messages in the store are the raw socket payload, not the normalised shape from Chat Messages:

{
  id: "msg_abc123",
  message: "Hello world",
  user: {
    id: "user-uuid",
    displayName: "SomeUser",
    photoURL: "https://cdn.fishtank.live/avatars/someuser.png",
    customUsernameColor: "#966b9e",
    clan: null,
    endorsement: null,
  },
  metadata: {
    isAdmin: false,
    isMod: false,
    isFish: false,
    // ...
  },
  mentions: [{ displayName: "other", userId: "uuid" }],
  // ... more fields
}

Why use store over messages?

Capability chat.store chat.messages
Receive new messages
Modify messages before render
Remove messages from chat
Normalised data ❌ (raw shape)
TTS/SFX deduplication ❌ (mixed in chat)
Multi-room support ❌ (current room only) ✅ (with chat.rooms)
Works in content scripts ❌ (page realm only)

Use chat.store when you need to filter, modify, or remove messages — for example, anti-spam, custom word filters, or hiding message types. Use chat.messages when you just want to log or react to messages, especially across multiple rooms.

Reliability notes

The fiber traversal path the SDK uses to find the store may be affected by site refactors. The SDK tries a fast known path first, then falls back to a broader tree search if that fails. If both fail, findStore() returns null and waitForStore() rejects after the timeout.

If the chat component is unmounted and remounted (rare but possible), call chat.store.reset() to clear the cached reference and force a fresh search on the next call.

Clone this wiki locally