Browser WebSocket
使用原生 WebSocket API 直接从网页浏览器连接到 finlight.me WebSocket API。这非常适合仪表盘、交易终端,以及任何需要实时文章更新且无需服务器端代理的前端应用。
浏览器的 WebSocket API 不支持自定义标头。认证通过查询参数而非 x-api-key 标头处理。您的 API 密钥将出现在 WebSocket URL 中 —— 请仅在可接受此情况的环境中使用此方式(例如经过认证的仪表盘)。切勿在面向公众的客户端代码中暴露您的 API 密钥。
工作原理
- 连接到
wss://wss.finlight.me(增强版)或wss://wss.finlight.me/raw(原始),并将您的 API 密钥作为查询参数 - 订阅:发送一条包含您过滤条件和
clientNonce的 JSON 消息 - 接收:收到一条确认您连接的
admit消息,随后在文章到达时收到sendArticle消息 - 保活:每 25 秒发送一次
ping消息 - 断开:调用
ws.close()
连接 URL
- Name
增强版- Type
- wss://wss.finlight.me
- Description
带完整增强的文章:情绪分析、实体提取、类别和完整内容(取决于您的订阅层级)。
- Name
原始- Type
- wss://wss.finlight.me/raw
- Description
不含增强的低延迟原始文章。最适合速度优先的用例,即您需要尽可能快地获取文章。
查询参数
- Name
apiKey- Type
- string
- Description
您的 finlight API 密钥。由于浏览器 WebSocket 无法设置自定义标头,因此用于认证。
- Name
takeover- Type
- string
- Description
设置为
"true"可在达到并发连接上限时自动替换最旧的连接。
- Name
clientVersion- Type
- string
- Description
您客户端应用的可选标识符。例如
"my-dashboard/1.0"
订阅消息
连接打开后,发送一条 JSON 消息以订阅文章。服务器会以一条 admit 消息响应,然后开始推送匹配的文章。
- Name
clientNonce- Type
- string
- Description
本次订阅请求的唯一标识符(例如 UUID)。服务器会在
admit响应中回传它。
- Name
query- Type
- string
- Description
用于查找相关文章的搜索查询。支持高级查询。
- Name
sources- Type
- string[]
- Description
按来源域名过滤。例如
["www.reuters.com", "www.cnbc.com"]
- Name
excludeSources- Type
- string[]
- Description
排除特定来源域名。
- Name
tickers- Type
- string[]
- Description
按股票代码过滤。例如
["AAPL", "NVDA"](仅限增强版)
- Name
countries- Type
- string[]
- Description
按 ISO 3166-1 alpha-2 国家/地区代码过滤。例如
["US", "DE"](仅限增强版)
服务器消息
服务器发送带有 action 字段的 JSON 消息,用于指示消息类型:
- Name
admit- Type
- object
- Description
在握手成功后发送。包含
leaseId、serverNow(时间戳)和您的clientNonce。
- Name
sendArticle- Type
- object
- Description
与您订阅匹配的新文章。文章数据位于
data字段中。
- Name
pong- Type
- object
- Description
对您
ping心跳的响应。
- Name
preempted- Type
- object
- Description
您的连接被另一个会话替换(当另一个客户端以
takeover: true连接时)。
- Name
error- Type
- object
- Description
发生了错误。请检查
data或error字段以获取详情。
完整示例
一个最小的、与框架无关的浏览器 WebSocket 客户端,带有自动重连和心跳:
- 通过查询参数进行认证
- 连接时发送订阅消息
- 维持 25 秒的心跳间隔
- 使用指数退避重连(500 毫秒到 10 秒)
- 处理所有服务器消息类型
Browser Client
const API_KEY = 'YOUR_API_KEY'
const WSS_URL = 'wss://wss.finlight.me' // Use '/raw' path for raw articles
let ws = null
let pingInterval = null
let reconnectTimeout = null
let reconnectAttempt = 0
function connect(filters = {}) {
const params = new URLSearchParams({
apiKey: API_KEY,
takeover: 'true',
clientVersion: 'my-app/1.0',
})
const url = `${WSS_URL}?${params}`
ws = new WebSocket(url)
ws.onopen = () => {
console.log('Connected')
reconnectAttempt = 0
// Send subscription with filters
ws.send(JSON.stringify({
clientNonce: crypto.randomUUID(),
query: filters.query || '',
language: filters.language || 'en',
sources: filters.sources || [],
tickers: filters.tickers || [],
countries: filters.countries || [],
}))
// Start heartbeat
pingInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
action: 'ping',
t: Date.now(),
}))
}
}, 25000)
}
ws.onmessage = (event) => {
const msg = JSON.parse(event.data)
switch (msg.action) {
case 'admit':
console.log('Admitted, lease:', msg.leaseId)
break
case 'sendArticle':
console.log('Article:', msg.data.title)
// Handle the article here
break
case 'pong':
break // Heartbeat OK
case 'preempted':
console.warn('Connection replaced')
break
case 'error':
console.error('Server error:', msg.data || msg.error)
break
}
}
ws.onclose = (event) => {
clearInterval(pingInterval)
// Don't reconnect on policy violations
if (event.code === 1008 || event.code === 4002) {
console.error('Connection blocked')
return
}
// Exponential backoff reconnect
const delay = Math.min(500 * 2 ** reconnectAttempt, 10000)
reconnectAttempt++
console.log(`Reconnecting in ${delay}ms...`)
reconnectTimeout = setTimeout(() => connect(filters), delay)
}
ws.onerror = () => console.error('WebSocket error')
}
function disconnect() {
clearInterval(pingInterval)
clearTimeout(reconnectTimeout)
if (ws) ws.close(1000)
}
// Usage
connect({ query: 'Nvidia', language: 'en', countries: ['US'] })
admit Response
{
"action": "admit",
"leaseId": "a1b2c3d4-e5f6-4789-abcd-ef0123456789",
"serverNow": 1708185600000,
"clientNonce": "your-uuid-here"
}
sendArticle Response
{
"action": "sendArticle",
"data": {
"link": "https://www.reuters.com/technology/nvidia-2026-02-17",
"source": "www.reuters.com",
"title": "Nvidia Reports Record Revenue",
"summary": "Nvidia announced record quarterly revenue...",
"publishDate": "2026-02-17T10:30:00Z",
"language": "en",
"sentiment": "positive",
"confidence": 0.92,
"countries": ["US"],
"categories": ["markets", "technology"],
"companies": [
{
"name": "NVIDIA Corporation",
"ticker": "NVDA",
"country": "US"
}
]
}
}
增强版与原始对比
| 功能 | 增强版 (/) | 原始 (/raw) |
|---|---|---|
| 情绪分析 | 是 | 否 |
| 实体提取(公司) | 是(取决于层级) | 否 |
| 类别 | 是 | 否 |
| 完整内容 | 是(取决于层级) | 否 |
| 延迟 | 标准 | 最低 |
过滤器:tickers | 是 | 否 |
过滤器:countries | 是 | 否 |
过滤器:query、sources、language | 是 | 是 |
原始 WebSocket 订阅仅支持 query、sources、excludeSources 和 language 过滤器。在原始端点上,股票代码和国家/地区过滤器会被静默忽略。