This commit is contained in:
2026-09-24 16:25:22 +08:00
commit 7428184f01
1198 changed files with 314515 additions and 0 deletions
@@ -0,0 +1,7 @@
{
"minSdkVersion": "21",
"dependencies":[
"com.squareup.okhttp3:okhttp-sse:3.12.2",
"com.squareup.okhttp3:okhttp:3.12.2"
]
}
@@ -0,0 +1,66 @@
import SSEClient from 'uts.sdk.modules.xSseUtsModule.SSEClient';
import {xSSEOptions} from "../interface.uts"
export class SSEClientApp {
private sseClient : SSEClient
url : string = ''
private onOpenEvt = () => { }
private onErrorEvt = () => { }
private onClosedEvt = () => { }
private onMessageEvt = (data : string) => { }
constructor(options:xSSEOptions) {
this.url = options.url;
let _headers = new Map<string, string>()
const headersOpts = options?.header??({} as UTSJSONObject)
for(const key in headersOpts){
_headers.set(key,headersOpts.getString(key)!)
}
this.sseClient = SSEClient(this.url, _headers)
}
@UTSJS.keepAlive
onOpen(callback : () => void) {
this.onOpenEvt = callback
}
@UTSJS.keepAlive
onError(callback : () => void) {
this.onErrorEvt = callback
}
@UTSJS.keepAlive
onClosed(callback : () => void) {
this.onClosedEvt = callback
}
@UTSJS.keepAlive
onMessage(callback : (data : string) => void) {
this.onMessageEvt = callback
}
connect() {
if(this.isConnected()) return;
this.sseClient.connect()
let _this = this;
class sslins implements SSEClient.SSEListener {
override onOpen() {
// console.log("SSE", "连接已打开")
_this.onOpenEvt()
}
override onMessage(event : string | null, data : string) {
_this.onMessageEvt(data)
}
override onError(throwable : Throwable) {
console.log("SSE", "发生错误", throwable)
_this.onErrorEvt()
}
override onClosed() {
// console.log("SSE", "连接已关闭")
_this.onClosedEvt()
}
}
this.sseClient.setListener(new sslins())
}
disconnect() {
this.sseClient.disconnect()
}
isConnected() : boolean {
return this.sseClient.isConnected();
}
}
@@ -0,0 +1,87 @@
package uts.sdk.modules.xSseUtsModule
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.sse.EventSource
import okhttp3.sse.EventSourceListener
import okhttp3.sse.EventSources
import java.util.concurrent.TimeUnit
class SSEClient(private val url: String, private val headers: Map<String, String> = mapOf()) {
private var eventSource: EventSource? = null
private var isConnected = false
private var listener: SSEListener? = null
// 连接状态回调接口
interface SSEListener {
fun onOpen()
fun onMessage(event: String?, data: String)
fun onError(throwable: Throwable)
fun onClosed()
}
private val client = OkHttpClient.Builder()
.readTimeout(0, TimeUnit.SECONDS) // SSE 需要禁用读取超时
.retryOnConnectionFailure(true)
.build()
fun setListener(listener: SSEListener) {
this.listener = listener
}
fun connect() {
if (isConnected) return
val request = Request.Builder()
.url(url)
.header("Accept", "text/event-stream")
.header("Cache-Control", "no-cache")
// 添加自定义头信息
headers.forEach { (key, value) ->
request.header(key, value)
}
val builtRequest = request.build()
eventSource = EventSources.createFactory(client)
.newEventSource(builtRequest, object : EventSourceListener() {
override fun onOpen(eventSource: EventSource, response: Response) {
isConnected = true
listener?.onOpen()
}
override fun onEvent(
eventSource: EventSource,
id: String?,
type: String?,
data: String
) {
listener?.onMessage(type, data)
}
override fun onClosed(eventSource: EventSource) {
isConnected = false
listener?.onClosed()
}
override fun onFailure(
eventSource: EventSource,
t: Throwable?,
response: Response?
) {
isConnected = false
t?.let { listener?.onError(it) }
}
})
}
fun disconnect() {
eventSource?.cancel()
eventSource = null
isConnected = false
}
fun isConnected(): Boolean = isConnected
}
@@ -0,0 +1,5 @@
{
"dependencies": {
"x_sse_s": "./lib/x_sse_s.har"
}
}
@@ -0,0 +1,55 @@
import { x_SSEClient } from "x_sse_s";
import {xSSEOptions} from "../interface.uts"
export class SSEClientApp {
private eventSource : x_SSEClient | null = null;
private url : string;
private onOpenEvt = () => { }
private onErrorEvt = () => { }
private onClosedEvt = () => { }
private onMessageEvt = (data : string) => { }
private headers:Map<string,string>
constructor(options:xSSEOptions) {
this.url = options.url;
const headersOpts = options?.header??({} as UTSJSONObject)
this.headers = headersOpts.toMap()
}
// 打开连接
public connect() : void {
if (this.eventSource||this.isConnected()) {
console.error('已有打开的,不允许重复');
return;
}
this.eventSource = new x_SSEClient(this.url,this.headers);
this.eventSource.onOpen(this.onOpenEvt)
this.eventSource.onError(this.onErrorEvt)
this.eventSource.onClosed(this.onClosedEvt)
this.eventSource.onMessage(this.onMessageEvt)
this.eventSource.connect();
}
onOpen(callback:()=>void){
this.onOpenEvt = callback
}
onError(callback:()=>void){
this.onErrorEvt = callback
}
onClosed(callback:()=>void){
this.onClosedEvt = callback
}
onMessage(callback:(data:string)=>void){
this.onMessageEvt = callback
}
// 关闭连接
public disconnect() : void {
if (!this.eventSource) {
return;
}
this.eventSource.disconnect();
this.onClosedEvt()
this.eventSource = null;
}
isConnected():boolean {
return this.eventSource?.isConnected()??false
}
}
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
</dict>
</plist>
@@ -0,0 +1,73 @@
import Foundation
class SSEClient: NSObject {
private var url: URL
private var headers: [String: String]
private var eventSourceTask: URLSessionDataTask?
private var onMessageCallback: ((String) -> Void)?
private var onOpenCallback: (() -> Void)?
private var onErrorCallback: ((Error?) -> Void)?
init(url: URL, headers: [String: String] = [:]) {
self.url = url
self.headers = headers
super.init()
}
func onMessage(_ callback: @escaping (String) -> Void) {
onMessageCallback = callback
}
func onOpen(_ callback: @escaping () -> Void) {
onOpenCallback = callback
}
func onError(_ callback: @escaping (Error?) -> Void) {
onErrorCallback = callback
}
func start() {
let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
var request = URLRequest(url: url)
// SSE
request.addValue("text/event-stream", forHTTPHeaderField: "Accept")
request.addValue("no-cache", forHTTPHeaderField: "Cache-Control")
//
for (key, value) in headers {
request.addValue(value, forHTTPHeaderField: key)
}
eventSourceTask = session.dataTask(with: request)
eventSourceTask?.resume()
}
func stop() {
eventSourceTask?.cancel()
}
}
extension SSEClient: URLSessionDataDelegate {
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
guard let string = String(data: data, encoding: .utf8) else { return }
let lines = string.components(separatedBy: .newlines)
for line in lines {
if line.isEmpty { continue }
if line.hasPrefix("data: ") {
let message = line.replacingOccurrences(of: "data: ", with: "")
onMessageCallback?(message)
}
}
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
completionHandler(.allow)
onOpenCallback?()
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
onErrorCallback?(error)
}
}
@@ -0,0 +1,3 @@
{
"deploymentTarget": "12"
}
@@ -0,0 +1,117 @@
import { URL } from 'Foundation';
import { Dictionary } from "Swift";
import {xSSEOptions} from "../interface.uts"
export class SSEClientApp {
private sseClient : SSEClient
public url : string = ''
private closed:boolean = true;
private onOpenEvt = () => { }
private onErrorEvt = () => { }
private onClosedEvt = () => { }
private onMessageEvt = (data : string) => { }
constructor(options:xSSEOptions) {
this.url = options.url;
let _headers = new Map<string, string>()
const headersOpts = (options.header!=null?(options.header as UTSJSONObject):({} as UTSJSONObject))
for(const key in headersOpts){
_headers.set(key,headersOpts.getString(key)!)
}
this.sseClient = new SSEClient(url = new URL(string = this.url)!, headers = _headers)
}
@UTSJS.keepAlive
onOpen(callback:()=>void){
this.onOpenEvt = callback
}
@UTSJS.keepAlive
onError(callback:()=>void){
this.onErrorEvt = callback
}
@UTSJS.keepAlive
onClosed(callback:()=>void){
this.onClosedEvt = callback
}
@UTSJS.keepAlive
onMessage(callback:(data:string)=>void){
this.onMessageEvt = callback
}
connect(headers:UTSJSONObject = {}) {
let _headers = new Map<string, string>()
for(const key in headers){
_headers.set(key,headers.getString(key)!)
}
console.log(_headers.size)
if(!this.closed) return;
this.sseClient = new SSEClient(url = new URL(string = this.url)!, headers = _headers)
this.closed = true;
this.sseClient.start()
let _this = this;
this.sseClient.onMessage((data:string)=>{
_this.onMessageEvt(data)
})
this.sseClient.onOpen(()=>{
_this.onOpenEvt()
_this.closed = false;
})
this.sseClient.onError((error:any|null)=>{
_this.onErrorEvt()
console.error(error)
_this.closed = true;
})
}
disconnect() {
this.sseClient.stop()
this.closed = true;
this.onClosedEvt()
}
isConnected() : boolean {
return !this.closed;
}
}
/**
// 在 ViewController 中使用
class ViewController: UIViewController {
private var sseClient: SSEClient?
override func viewDidLoad() {
super.viewDidLoad()
// 初始化 SSE 客户端
if let url = URL(string: "https://your-sse-server.com/events") {
sseClient = SSEClient(url: url)
sseClient?.delegate = self
sseClient?.connect()
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
sseClient?.disconnect()
}
}
// 实现代理方法
extension ViewController: SSEClient.EventSourceDelegate {
func eventSourceDidOpen() {
print("SSE 连接已打开")
}
func eventSourceDidReceiveMessage(event: String?, data: String) {
print("收到消息: event=\(event ?? ""), data=\(data)")
}
func eventSourceDidError(_ error: Error) {
print("发生错误: \(error.localizedDescription)")
}
func eventSourceDidClose() {
print("连接已关闭")
}
}
*/
+4
View File
@@ -0,0 +1,4 @@
export type xSSEOptions = {
url:string,
header?:UTSJSONObject
}
@@ -0,0 +1,5 @@
export class SSEClientApp {
constructor() {
}
}
+81
View File
@@ -0,0 +1,81 @@
import {xSSEOptions} from "../interface.uts"
export class SSEClientApp {
private eventSource : EventSource | null = null;
private url : string;
private onOpenEvt = () => { }
private onErrorEvt = () => { }
private onClosedEvt = () => { }
private onMessageEvt = (data : string) => { }
private headers:Map<string,string>
constructor(options:xSSEOptions) {
this.url = options.url;
let _headers = new Map<string, string>()
const headersOpts = options?.header??({} as UTSJSONObject)
for(const key in headersOpts){
_headers.set(key,headersOpts.getString(key)!)
}
this.headers = _headers
}
// 打开连接
public connect() : void {
if (this.eventSource||this.isConnected()) {
console.error('已有打开的,不允许重复');
return;
}
let _this = this;
let params = ""
this.headers.forEach((value,key)=>{
params += `${key}=${value}&`
})
if(this.headers.size>0){
params = params.substring(0,params.length-1)
}
let urlParams = this.url;
if(params.length>0){
urlParams = this.url+"?"+params
}
this.eventSource = new EventSource(urlParams);
this.eventSource.onopen = (event) => {
_this.onOpenEvt()
};
this.eventSource.onerror = (event) => {
_this.onErrorEvt()
console.error("SSE", "发生错误", event);
};
this.eventSource.onmessage = (event) => {
_this.onMessageEvt(event.data)
};
}
onOpen(callback:()=>void){
this.onOpenEvt = callback
}
onError(callback:()=>void){
this.onErrorEvt = callback
}
onClosed(callback:()=>void){
this.onClosedEvt = callback
}
onMessage(callback:(data:string)=>void){
this.onMessageEvt = callback
}
// 关闭连接
public disconnect() : void {
if (!this.eventSource) {
return;
}
this.eventSource.close();
this.onClosedEvt()
this.eventSource = null;
}
isConnected():boolean {
let status = this.eventSource?.readyState??2
return status == 1
}
}