U
    7^j                     @   s   d Z ddlmZmZ ddlmZmZmZmZ ddl	m
Z
 ddlmZ ddlmZ edgd	ZG d
d dZe Ze
eedddZededdfeeedddZdS )uR  WebSocket 文本转发。

设备 A、B 各自带自己的 API Key 连到同一个 channel，
任一端发送的文本消息会被转发给同频道内的其他设备。

连接 URL 形如：
    ws://host/ws/{channel}?api_key=pica_xxx

API Key 放在 query 而非 header，因为浏览器 WebSocket 客户端
无法自定义请求头。
    )DictSet)	APIRouter	WebSocketWebSocketDisconnectQuery)Session   )SessionLocal)ApiKey	websocket)tagsc                   @   sd   e Zd ZdZddddZeeddddZeeddd	d
ZeeddddZ	ee
dddZdS )ConnectionManageru   按 channel 维护活跃连接，负责频道内消息转发。

    同一个 channel 下的连接互为对端：A 发的消息会推给 B（及频道内
    其他人），不会回推给 A 自己。
    N)returnc                 C   s
   i | _ d S N)	_channels)self r   /root/pica-gateway/app/ws.py__init__   s    zConnectionManager.__init__)channelwsr   c                    s(   |  I d H  | j|t | d S r   )acceptr   
setdefaultsetadd)r   r   r   r   r   r   connect"   s    zConnectionManager.connectc                 C   s4   | j |}|sd S || |s0| j |d  d S r   )r   getdiscardpop)r   r   r   Zconnsr   r   r   
disconnect&   s    
zConnectionManager.disconnect)r   senderr   c              	      s   g }| j |t D ]f}||kr"qz8t|ttfrH|t|I dH  n||I dH  W q tk
rx   |	| Y qX q|D ]}| 
|| qdS )u   把消息转发给同频道内除发送者以外的所有设备。

        message 可以是 str（文本/信令）或 bytes（裸 opus 等二进制帧），
        按原始类型原样转发，不做任何解析。
        N)r   r   r   
isinstancebytes	bytearray
send_bytes	send_text	Exceptionappendr    )r   r   r!   messagedeadr   r   r   r   	broadcast.   s    zConnectionManager.broadcast)r   r   c                 C   s   t | j|t S r   )lenr   r   r   )r   r   r   r   r   channel_sizeC   s    zConnectionManager.channel_size)__name__
__module____qualname____doc__r   strr   r   r    r+   intr-   r   r   r   r   r      s   r   )dbraw_keyr   c                 C   sB   |sdS t |}| t t j|k }|dks:|jr>dS dS )uH   校验 API Key：明文 → SHA-256 → 查库 → 命中且未吊销。FNT)r   hash_keyqueryfilterkey_hashfirstrevoked)r4   r5   r9   api_keyr   r   r   authenticate_api_keyJ   s    
r=   z/ws/{channel} )default)r   r   r<   c           	         s  t  }zt||}W 5 |  X |s:| jddI dH  dS t|| I dH  td| d| j dt| dd zz| 	 I dH }|d
 dkrqNd|kr|d }|dd }td| d| j dt| dt
| d|
dd t|| |I dH  qv|dp
d}td| d| j dt| d|dd t|| |I dH  qvW n tk
rf   Y nX W 5 t||  td	| d| j dt| dd X dS )u&  频道内文本转发端点。

    流程：
    1. 用 query 里的 api_key 鉴权；失败则用 1008（policy violation）关闭。
    2. 鉴权通过后 accept，加入频道。
    3. 循环收文本消息，转发给同频道其他设备。
    4. 对端断开时从频道摘除。
    i  )codeNz[relay] join channel=z client=z size=T)flushz[relay] leave channel=typezwebsocket.disconnectr#       z[relay] bin channel=z from=z bytes=z head=textr>   z[relay] msg channel=z msg=)r
   closer=   managerr   printclientr-   r    receiver,   r+   r   r   )	r   r   r<   r4   okmsgdataZpreviewrD   r   r   r   websocket_endpointU   sJ    
,"
rM   N)r1   typingr   r   fastapir   r   r   r   sqlalchemy.ormr   databaser
   modelsr   routerr   rF   r2   boolr=   r   rM   r   r   r   r   <module>   s    0