npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@yinshan-tech/webrtc-react

v0.8.6

Published

React UI for @yinshan-tech/webrtc-sdk Softphone: button, call widget, hooks (no antd)

Readme

@yinshan-tech/webrtc-react

@yinshan-tech/webrtc-sdk 的 React UI(当前 0.8.6)。无 antd。 催收逾期、电销案件仍由宿主注入。电话壳文案(中/英/印尼)在本库,宿主传 locale。后台入口是 SoftphoneHost + createCallCenterSoftphone,不要手写一套 Provider / toast / 接线。

规范见 docs/conventions.md

安装

npm i @yinshan-tech/webrtc-react@^0.8 @yinshan-tech/webrtc-sdk@^0.8

本仓 peerDependencieswebrtc-sdk@^0.8.9predictive-task / predictive-occupancy SoftphoneEvent)。只克隆本仓即可 npm i / npm publish,不要把 file:../webrtc-sdk 写进对外文档。

不 bump 本仓库版本,除非明确要求。

只在既不挂 CallWidget / OutboundModeShell、也不 ensureCallWidgetHost 时才手动:

import '@yinshan-tech/webrtc-react/styles.css';

表面二选一

CallWidgetOutboundModeShell 不要同时挂OutboundCallButtonautoMountWidget 默认 false

| 表面 | 何时用 | |------|--------| | OutboundModeShell | 点呼 + 渐进 / 预测工作台(ap-admin 现网) | | CallWidget | 只要一张浮动通话卡片 |

最小用法(CallWidget)

import { createSoftphone } from '@yinshan-tech/webrtc-sdk';
import {
  SoftphoneProvider,
  OutboundCallButton,
  CallWidget,
  useSoftphoneConnect,
} from '@yinshan-tech/webrtc-react';

const phone = createSoftphone({ /* endpoints + adapters,见 webrtc-sdk README */ });

function Boot({ children }: { children: React.ReactNode }) {
  useSoftphoneConnect();
  return <>{children}</>;
}

<SoftphoneProvider phone={phone}>
  <Boot>
    <OutboundCallButton
      dialPayload={{ phoneNumber: '6281...' }}
      displayName="Customer"
    />
    <CallWidget locale="id-ID" />
  </Boot>
</SoftphoneProvider>

OutboundCallButton 禁用 = origin 锁(本 origin 活 SIP 或渐进 running 本标签弹窗还在(running / paused / 完成倒计时)。完成倒计时不占 origin 锁。HTTP 进行中也会 busy。点呼前用 phone.isReadyForOutbound(),不要用 getRegistration() === 'registered' 跳过 connect()connect() 抛错也走 onResult。宿主可在 start() 未完成时卸按钮(点呼后立刻关选号弹窗);库不在卸载后 setState

后台最少对接

点呼-only 新后台:SoftphoneHost + createCallCenterSoftphone。库不建坐席、不拼媒体域名、不拉案件、不打开 tab。宿主实现 seat / media / HTTP / 案件 fetch / 开 tab

渐进 hooks(useProgressiveSession / useProgressiveOverlay)与 OutboundModeShell 分开。壳继续只做 chrome + renderBusinessInfo。不要把 fetch 或开 tab 写进壳。

import {
  SoftphoneHost,
  OutboundModeShell,
  createCallCenterSoftphone,
  createSoftphoneHttp,
} from '@yinshan-tech/webrtc-react';

const http = createSoftphoneHttp({
  getHeaders: () => ({
    'x-auth-token': token() ?? '',
    // 多产品宿主在这里带 x-product-code,不要写进库
  }),
});

function createPhone() {
  return createCallCenterSoftphone({
    callCenterPrefix: '/CALL_CENTER',
    media: hostMedia(), // controlWsUrl / sipDomain / sipWsUrl
    manualDialUrl: '/CALL_CENTER_BIZ/outbound/manual/dial',
    getAgentId: () => currentUser.id,
    getSeatConfig: (agentId) => hostSeat(agentId),
    getAuthToken: () => token(),
    http,
  });
}

<SoftphoneHost
  locale="id-ID"
  toast={hostToast}
  enabled={seatReady} // false → 只渲染 children,不 REGISTER
  createPhone={createPhone} // 单 UA;多 UA 用 rules + pathname
  callSurface={
    <OutboundModeShell
      mode="manual"
      locale="id-ID"
      renderBusinessInfo={({ uiContext }) => hostCaseCard(uiContext)}
    />
  }
>
  {children}
</SoftphoneHost>

渐进(宿主自己挂 hooks,不进壳):

import { useProgressiveSession, useProgressiveOverlay } from '@yinshan-tech/webrtc-react';

useProgressiveSession({
  onCustomer: (ctx) => hostOpenTab(ctx), // 路径由宿主拼
  canOpen: (ctx) => hostCanOpen(ctx),
  identityKey: (ctx) => hostIdentityKey(ctx),
});
const overlay = useProgressiveOverlay({
  fetch: (ctx) => hostFetchCase(ctx), // 案件 HTTP 留宿主
  keyOf: (ctx) => hostOverlayKey(ctx),
});
  • enabled === false 或(有 rules 且无匹配)→ 只渲染 children,不建 Softphone。何时 REGISTER 是宿主产品行为。
  • rules.match 为函数或字符串前缀。库内零宿主路径字面量。
  • createCallCenterSoftphonecallCenterPrefix 拼 hangup / make-call / 渐进 pause|resume|cancel|agent-view。创建 URL 是可选的 outboundTaskCreateUrl(与 manualDialUrl 同类,宿主全路径),不要从 prefix 拼。可选 parseCreatedTaskId 工厂一次注入;确定调 createOutboundTask,库调用钩子挂载。不写默认 Hive 解析。现网若不调这些方法,不会打对应 URL。默认 mergeDialSession = mergeTopLevelDialSessionruntime: 'tab-local'
  • HTTP 头全部来自 getHeaders()。库不写死 x-auth-token
  • waiting 页路径由宿主拼;库只认 isWaitingHostIdprogressive-waiting predictive-waiting)。
  • 列表页注册态用 useRegistrationOrUnavailable(Host 未挂 phone 时返回 'unavailable',不 throw)。外呼能否打用 useSoftphoneReady / phone.isReadyForOutbound(),不要把 === 'registered' 当门闩。
  • 点呼互斥用 useClickCallDisabled({ checkedIn, predictiveCompleting })(origin 锁 + 任务 running|paused + 本标签完成倒计时 + 可选预测签入/15s)。未传 checkedIn 默认 false,不灰点呼。按钮仍由宿主画。
  • 点呼成功用 bindDialHangupCallId(phone, result, parse?)。默认 parseHangupCallId;ys 传入读 providerCallId 的 parse。催记开窗留宿主。

不要 Host、只要一张卡时,用上面的 CallWidget 最小用法。

OutboundModeShell

manual | progressive | predictive 共用 chrome。业务内容走槽位,不要把逾期金额写进本库。

<OutboundModeShell
  mode="manual"
  locale="id-ID"
  formatPartyLine={(ctx) => String(ctx?.customerName ?? '')}
  renderBusinessInfo={({ uiContext }) => (
    <div>{String(uiContext?.overdueAmount ?? '')}</div>
  )}
/>
  • :壳 createPortaldocument.body(与 CallWidget 同一层)。默认 --wro-modal-z1100,压过 antd 业务 Modal(1000)。宿主业务弹窗若更高,给壳传 zIndexonRequestCancelTask 的确认框是对壳本身的决定,必须 高于壳(antd Modal.confirm 默认 1000 会被盖住,宿主自己设 zIndex,例如 1200)。只改数字、不 portal,压不过 #root 里的 stacking context。
  • 文案locale 选库内包(zh-CN / en-US / id-ID)。未知 locale 与未传时用 en-US不读 navigator.languagetexts 只覆盖单条。催收业务词不要放进本库。
  • ToastSoftphoneConnectToast + SoftphoneNotifyBridge 吃宿主 toast 适配器(无 antd)。connect 只报终态;lifecycle notify 忽略。点呼 / 外呼任务 / 挂断 HTTP 失败优先 notify.message(后端信封或响应体原文);SDK 英文兜底(Call failed, please retry 等)仍走三语 copy。客户端锁(origin_busy / already_in_call)和媒体保活失败(media_failed)只用 copy。预测签入错误 predictive_tab_busy / predictive_align_timeout / predictive_session_replaced / predictive_sip_active / predictive_enter_in_flight / sip_unregister_timeout 走库 copy。
  • 点呼密度:宿主决定。催收可传 formatManualRemoteNumber / formatPartyLine(姓名 + 掩码号 + 角色);电销只传姓名、不传号码 formatter。
  • 渐进:pause / resume / cancel、状态行、到期倒计时。startOutboundTask 后订阅 outbound-task。两通之间 SIP ended 不拆壳。状态行无具体相位时不再写「等待接通中」(空则 )。暂停且当前腿仍 dialingpausedStillDialing(已暂停 · 当前号码仍在拨打);仅暂停、或暂停后的 waiting_next,仍只「已暂停」。活 SIP(呼叫中 / 通话中)仍优先;failed 不压 waiting_next。未接通 interval 与已接通挂断一样,间隔 > 0 显示 {n}s。案件 overlay / 开 tab 走 hooks,不要把 fetch 焊进壳。resume await phone.resumeOutboundTask(),按钮 busy;失败走 SDK notify。hasActiveCall || isOriginBusy() 时灰掉(他标签点呼也禁)。宿主 Hive 创建走 SDK tryHoldOriginLock,打开选号弹窗不占锁。渐进开始不要用 registered 灰掉;恢复中可点,由 SDK createOutboundTask 等就绪。
  • 预测式任务壳mode="predictive" predictiveCheckedIn 时由宿主设置;签出立刻回 manualPredictiveTaskBody 是真按钮(签出 / 暂停 / 就绪),不包 ProgressiveTaskBody、无进度条。号码 / 姓名 / renderBusinessInfo 一直画;挂断后不读 stash.predictive,值走默认 -(非活相位不把 ended call 传给 formatter,避免号码回落)。签入后默认画静音 / 挂断(与渐进同套 callControls,未允许时禁用),不要闸成「有通话才渲染」。静音仅 phase==='active'canMuteCall)。挂断启用 canHangup || phase==='active'canHangupPredictiveCall),未桥接 / 振铃禁用,不要套渐进的 ringing 可挂。状态行走 resolvePredictiveStatusLine(15s → 活 SIP → WRAPUP TTL → 暂停空闲 → 五句就绪空闲 → 对齐中);空闲不得画 。wrap-up 倒计时是截止时刻,迟到 view / occupancy 不得把剩余秒数调大。X / 签出走 onRequestCancelTask(宿主确认后再 session.leave());15s 到期走 onPredictiveCheckout(直接 leave())。不要 cancelOutboundTask

槽位

| 槽 | 用途 | |----|------| | renderBusinessInfo | 宿主案件卡片(逾期等) | | formatPartyLine / formatManualPartyLine / formatProgressivePartyLine | 对方身份行 | | formatManualRemoteNumber | 点呼号码展示 | | onRequestCancelTask | 渐进:宿主确认后再 cancelOutboundTask。预测:X / 签出确认后再 session.leave() | | onPredictiveCheckout / onPredictivePause / onPredictiveReady | 预测:15s 到期直接 leave() / 暂停 / 就绪 | | onBlockedDismiss | 通话中点关闭 |

导出

Chrome: SoftphoneProviderOutboundCallButtonCallWidgetOutboundModeShelluseSoftphoneConnectuseCallSnapshotuseRegistrationuseRegistrationOrUnavailableuseSoftphoneReadyuseSoftphoneNotifyensureCallWidgetHostresolveSoftphoneTextsresolveNotifyMessageSoftphoneConnectToastSoftphoneNotifyBridge

接线: SoftphoneHostcreateCallCenterSoftphonecreateSoftphoneHttpcallCenterTaskEndpointsmergeTopLevelDialSessionparseHangupCallIdbindDialHangupCallIdresolveSoftphoneAssetsshouldDisableClickCallshouldDisableProgressiveStartshouldDisableProgressiveResumeuseClickCallDisabled

渐进槽(hooks,不是壳): flattenUiContextuseProgressiveSessionuseProgressiveOverlayisWaitingHostIdwaitingHostKind

预测式签入: createPredictiveSipSessionresolvePredictiveStatusLineshouldStartPredictiveTerminalCountdownPREDICTIVE_WAITING_CUSTOMER_ID

const session = createPredictiveSipSession({
  softphone: phone,
  defaultMedia: hostMedia(),          // A/B,createSoftphone 默认仍连这套
  predictiveMedia: hostPredictive(),  // C;不得与默认 SIP LB 相同
  http,
  statusUrl: `${prefix}/predictive/agent/status`,
  pauseUrl: `${prefix}/predictive/agent/pause`,
  readyUrl: `${prefix}/predictive/agent/ready`,
  checkoutUrl: `${prefix}/predictive/agent/checkout`,
  getExtNo: () => seat.account,
  getExtNoCallId: () => phone.getRegisteredCallId(),
});

await session.enter();   // 不因 Redis checkedIn 拒绝;本标签有渐进则不切 C。ok 才算预测 REGISTER + Redis 对齐
await session.pause();   // 只改 AVAIL;SIP 留 C;预测锁不放。通话中可暂停
await session.ready();   // 本标签活 SIP 允许就绪;他标签 origin 忙或本标签渐进则拒
await session.leave();   // hasActiveCall → already_in_call;否则 void POST checkout(keepalive,不等、不读 body)立刻 rollbackToDefault + release
session.dispose();       // Host 卸载必须调用:release 锁;仍签入则同样 fire-and-forget checkout

enter() 迁册只换 SIP(默认域 UNREGISTER → 预测域 REGISTER),不拆控制面 WS。失败(unregister 超时 / REG 失败 / align 超时)rollback 回默认 FS,宿主不得进等待页。已签入后的 SIP/控制面断线走现有保活(当前预测域),只有 leave() 才切回默认。idempotent 只表示本标签已持锁,不是签入成功。

leave() / pagehide 禁止 if (!httpOk) return。失败不 toast。签出后宿主留在当前 URL,不要 history.replace。pagehide 只在 enter 成功后绑定。

后台对接:createCallCenterSoftphone({ predictiveMedia }) 会挂 session + view poll。列表用 usePredictiveSnapshot().enter(),成功后由宿主 history.push 等待页。壳传 bindPredictive:pause / ready / 15s leave / modeSession 由库填。签入中全屏 wro-aligning-mask 挡点击。定时 GET /predictive/agent/view 是上一发(成功或失败)结束后再等 3s,不要墙钟 setInterval;签入 / 回前台 / 任务事件 / 振铃接通对账 / 挂断后 1s 仍立刻拉。

预测 FS 唯一入口是宿主手动 enter()。库不因等待页 URL / F5 / registered 自动 enter。库不写产品路由,不开催收详情 tab,不弹催记。

rollbackToDefaultcontinueAfterUnregisterTimeout:迁册超时不改端点;回默认超时仍切回并 REGISTER。

两把锁:PredictiveCheckInLock(同 origin + 同分机,enter→leave 一直持有,暂停不放)与 origin 锁(就绪占、暂停且无坐席 SIP 则放,reason 仍是 'task')。他标签渐进 paused 且无活 SIP 时允许 enter;他标签活 SIP(即使渐进已暂停)拒绝。第二页再 enter → predictive_tab_busy