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. |
||
|---|---|---|
| .. | ||
| src | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
@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