learning_ai_common_plat/packages/broadcast-client
saravanakumardb1 dd90f709e1 fix(gitea): set ROOT_URL=host.docker.internal, NO_PROXY for host (F17)
Resolves F17 in docker-build-optimization-roadmap.

Root cause:
  Gitea's app.ini ROOT_URL was http://localhost:3300/. Gitea bakes
  ROOT_URL into the dist.tarball field of every published package's
  metadata. Inside a Docker container, 'localhost' is the container
  itself, not the host \u2014 so any 'pnpm install' that needed to fetch
  a tarball would ECONNREFUSED, even though the registry metadata
  itself was reachable via host.docker.internal.

Server-side fix (not in git, requires manual replication on each dev
machine; documented in roadmap \u00a73 A-pre-6):
  - Edit /opt/homebrew/var/gitea/custom/conf/app.ini:
    ROOT_URL = http://host.docker.internal:3300/
  - brew services restart gitea
  - sudo sh -c 'echo "127.0.0.1 host.docker.internal" >> /etc/hosts'

Repo-side fix (this commit):
  - switch-network.sh: add host.docker.internal to NO_PROXY +
    NPM_CONFIG_NOPROXY when NETWORK=corp. Required so host-side curl/
    pnpm/npm bypass the corporate proxy (cso.proxy.att.com) when
    resolving host.docker.internal. Without this, host installs fail
    with the corp proxy's 'Unknown Host' 504 page.

Republished all 64 @bytelyst/* packages so tarball URLs reflect the
new ROOT_URL:
  - .publish-manifest.json: 64 entries with new content hashes
  - packages/*/package.json: 64 patch-version bumps
    (auto-bumped by publish-outdated-packages.sh because previous
    versions already existed in registry)

Verification:
  curl http://localhost:3300/.../@bytelyst%2Ferrors | jq .dist.tarball
  → http://host.docker.internal:3300/.../errors-0.1.11.tgz  (was localhost:3300)
  workspace:* refs across all 64 packages: 0

Unblocks: A0-V on every pilot. Verified PASSING on learning_ai_clock:
  backend cold build: 59.2 s
  web cold build:     3:13 (193 s)
  Both via Gitea registry, no docker-prep.sh tarballs needed.
2026-05-27 01:51:43 -07:00
..
src chore(broadcast-client): document deep-link diagnostics 2026-05-04 16:07:13 -07:00
package.json fix(gitea): set ROOT_URL=host.docker.internal, NO_PROXY for host (F17) 2026-05-27 01:51:43 -07:00
README.md docs(packages): Add comprehensive README for broadcast-client and survey-client 2026-03-03 08:29:07 -08:00
tsconfig.json feat(packages): Phase 3.1 - Create @bytelyst/broadcast-client package 2026-03-03 07:34:39 -08:00

@bytelyst/broadcast-client

TypeScript client for the ByteLyst Broadcast & Messaging platform. Provides in-app message polling, read receipts, and push notification token management.

Installation

npm install @bytelyst/broadcast-client
# or
pnpm add @bytelyst/broadcast-client

Quick Start

import { createBroadcastClient } from '@bytelyst/broadcast-client';

const client = createBroadcastClient({
  baseURL: 'https://api.bytelyst.io/v1',
  productId: 'lysnrai',
  getAuthToken: async () => {
    // Return your JWT token
    return localStorage.getItem('token');
  }
});

// Start polling for messages (every 60 seconds)
client.startPolling(60000, (messages) => {
  console.log('New messages:', messages);
});

API Reference

createBroadcastClient(config)

Creates a new broadcast client instance.

Config:

Option Type Required Description
baseURL string Yes API base URL
productId string Yes Product identifier
getAuthToken () => Promise Yes Function to retrieve JWT token

Methods

getMessages()

Fetch active in-app messages for the current user.

const { data, error } = await client.getMessages();
// Returns: { messages: InAppMessage[] }

markRead(messageId: string)

Mark a message as read.

await client.markRead('msg_123');

markDismissed(messageId: string)

Dismiss a message.

await client.markDismissed('msg_123');

trackClick(messageId: string)

Track when user clicks/taps a message CTA.

await client.trackClick('msg_123');

startPolling(intervalMs: number, callback: (messages) => void)

Start polling for new messages.

client.startPolling(60000, (messages) => {
  // Called every 60 seconds with current messages
});

stopPolling()

Stop message polling.

client.stopPolling();

registerDeviceToken(token: string, platform: 'ios' | 'android' | 'web')

Register push notification device token.

await client.registerDeviceToken('fcm_token_xyz', 'android');

unregisterDeviceToken(token: string)

Unregister device token (e.g., on logout).

await client.unregisterDeviceToken('fcm_token_xyz');

React Integration

Hook Usage

import { useBroadcastClient } from './hooks/useBroadcastClient';

function App() {
  const { messages, unreadCount, markRead, markDismissed } = useBroadcastClient({
    pollingInterval: 60000
  });

  return (
    <div>
      {messages.map(msg => (
        <Banner
          key={msg.id}
          title={msg.title}
          body={msg.body}
          onDismiss={() => markDismissed(msg.id)}
          onClick={() => markRead(msg.id)}
        />
      ))}
    </div>
  );
}

Provider Pattern

// BroadcastProvider.tsx
import { createContext, useContext, useEffect, useState } from 'react';
import { createBroadcastClient, BroadcastClient, InAppMessage } from '@bytelyst/broadcast-client';

const BroadcastContext = createContext<BroadcastContextType | null>(null);

export function BroadcastProvider({ children, config }: { children: React.ReactNode; config: BroadcastConfig }) {
  const [client] = useState(() => createBroadcastClient(config));
  const [messages, setMessages] = useState<InAppMessage[]>([]);

  useEffect(() => {
    client.startPolling(60000, setMessages);
    return () => client.stopPolling();
  }, [client]);

  const value = {
    messages,
    unreadCount: messages.filter(m => m.status === 'unread').length,
    markRead: client.markRead.bind(client),
    markDismissed: client.markDismissed.bind(client),
    trackClick: client.trackClick.bind(client),
  };

  return (
    <BroadcastContext.Provider value={value}>
      {children}
    </BroadcastContext.Provider>
  );
}

export const useBroadcast = () => {
  const ctx = useContext(BroadcastContext);
  if (!ctx) throw new Error('useBroadcast must be used within BroadcastProvider');
  return ctx;
};

Types

interface InAppMessage {
  id: string;
  broadcastId: string;
  title: string;
  body?: string;
  style: 'banner' | 'modal' | 'fullscreen' | 'toast';
  priority: 'low' | 'normal' | 'high' | 'urgent';
  ctaText?: string;
  ctaUrl?: string;
  imageUrl?: string;
  deepLink?: {
    screen: string;
    params: Record<string, string>;
  };
  status: 'unread' | 'read' | 'dismissed';
  createdAt: string;
}

interface BroadcastConfig {
  baseURL: string;
  productId: string;
  getAuthToken: () => Promise<string>;
}

Error Handling

All methods return a result tuple [data, error]:

const [data, error] = await client.getMessages();

if (error) {
  console.error('Failed to fetch messages:', error.message);
  return;
}

// Use data.messages

Browser Support

  • Chrome 90+
  • Firefox 88+
  • Safari 14+
  • Edge 90+

License

MIT © ByteLyst