@skopon-cool/form-sdk 0.2.5 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/catalog/a2uiCustomCatalog.d.ts.map +1 -1
- package/dist/catalog/skoponCaseSelect.d.ts.map +1 -1
- package/dist/catalog/skoponResumeSelect.d.ts.map +1 -1
- package/dist/catalog/useSkoponBoundField.d.ts +16 -1
- package/dist/catalog/useSkoponBoundField.d.ts.map +1 -1
- package/dist/client/authStorage.d.ts +8 -0
- package/dist/client/authStorage.d.ts.map +1 -1
- package/dist/components/AskUserFormCard.d.ts +6 -1
- package/dist/components/AskUserFormCard.d.ts.map +1 -1
- package/dist/components/SkoponFormRenderer.d.ts +1 -0
- package/dist/components/SkoponFormRenderer.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1520 -1081
- package/dist/runtime/formAutoControl.d.ts +16 -0
- package/dist/runtime/formAutoControl.d.ts.map +1 -0
- package/dist/runtime/formRuntimeContext.d.ts +21 -0
- package/dist/runtime/formRuntimeContext.d.ts.map +1 -0
- package/dist/runtime/formRuntimeWs.d.ts +27 -0
- package/dist/runtime/formRuntimeWs.d.ts.map +1 -0
- package/dist/runtime/types.d.ts +20 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/catalog/a2uiCustomCatalog.tsx +41 -16
- package/src/catalog/skoponCaseSelect.tsx +10 -4
- package/src/catalog/skoponResumeSelect.tsx +10 -4
- package/src/catalog/useSkoponBoundField.ts +100 -4
- package/src/client/authStorage.test.ts +34 -0
- package/src/client/authStorage.ts +27 -0
- package/src/components/AskUserFormCard.tsx +164 -31
- package/src/components/SkoponFormRenderer.tsx +12 -0
- package/src/index.ts +2 -0
- package/src/runtime/formAutoControl.test.ts +127 -0
- package/src/runtime/formAutoControl.ts +110 -0
- package/src/runtime/formRuntimeContext.tsx +129 -0
- package/src/runtime/formRuntimeWs.ts +158 -0
- package/src/runtime/types.ts +26 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FormAutoControlEventData,
|
|
3
|
+
FormRuntimeEnvelope,
|
|
4
|
+
FormRuntimeOutboundType,
|
|
5
|
+
} from './types'
|
|
6
|
+
|
|
7
|
+
const SEND_QUEUE_MAX = 100
|
|
8
|
+
const RECONNECT_MAX_MS = 30_000
|
|
9
|
+
|
|
10
|
+
export type FormAutoControlHandler = (
|
|
11
|
+
data: FormAutoControlEventData,
|
|
12
|
+
envelope: FormRuntimeEnvelope<FormAutoControlEventData>,
|
|
13
|
+
) => void
|
|
14
|
+
|
|
15
|
+
export interface FormRuntimeWsClientOptions {
|
|
16
|
+
url: string
|
|
17
|
+
askUserMessageId: string
|
|
18
|
+
onAutoControl?: FormAutoControlHandler
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class FormRuntimeWsClient {
|
|
22
|
+
private readonly url: string
|
|
23
|
+
private readonly askUserMessageId: string
|
|
24
|
+
private readonly onAutoControl?: FormAutoControlHandler
|
|
25
|
+
private ws: WebSocket | null = null
|
|
26
|
+
private closed = false
|
|
27
|
+
private reconnectAttempt = 0
|
|
28
|
+
private reconnectTimer: ReturnType<typeof setTimeout> | null = null
|
|
29
|
+
private sendQueue: string[] = []
|
|
30
|
+
|
|
31
|
+
constructor(options: FormRuntimeWsClientOptions) {
|
|
32
|
+
this.url = options.url
|
|
33
|
+
this.askUserMessageId = options.askUserMessageId
|
|
34
|
+
this.onAutoControl = options.onAutoControl
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
connect(): void {
|
|
38
|
+
if (this.closed || typeof WebSocket === 'undefined') return
|
|
39
|
+
this.clearReconnectTimer()
|
|
40
|
+
try {
|
|
41
|
+
const ws = new WebSocket(this.url)
|
|
42
|
+
this.ws = ws
|
|
43
|
+
ws.onopen = () => {
|
|
44
|
+
this.reconnectAttempt = 0
|
|
45
|
+
this.flushQueue()
|
|
46
|
+
}
|
|
47
|
+
ws.onmessage = (event) => {
|
|
48
|
+
this.handleMessage(event.data)
|
|
49
|
+
}
|
|
50
|
+
ws.onclose = () => {
|
|
51
|
+
this.ws = null
|
|
52
|
+
this.scheduleReconnect()
|
|
53
|
+
}
|
|
54
|
+
ws.onerror = () => {
|
|
55
|
+
// onclose will follow; avoid noisy logs
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.warn('[FormRuntimeWs] connect failed', error)
|
|
59
|
+
this.scheduleReconnect()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
send(
|
|
64
|
+
type: FormRuntimeOutboundType,
|
|
65
|
+
data: Record<string, unknown>,
|
|
66
|
+
): void {
|
|
67
|
+
if (this.closed) return
|
|
68
|
+
const payload: FormRuntimeEnvelope = {
|
|
69
|
+
type,
|
|
70
|
+
ask_user_message_id: this.askUserMessageId,
|
|
71
|
+
data,
|
|
72
|
+
}
|
|
73
|
+
let serialized: string
|
|
74
|
+
try {
|
|
75
|
+
serialized = JSON.stringify(payload)
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.warn('[FormRuntimeWs] serialize failed', error)
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
81
|
+
this.ws.send(serialized)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
this.enqueue(serialized)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
close(): void {
|
|
88
|
+
this.closed = true
|
|
89
|
+
this.clearReconnectTimer()
|
|
90
|
+
this.sendQueue = []
|
|
91
|
+
if (this.ws) {
|
|
92
|
+
this.ws.onopen = null
|
|
93
|
+
this.ws.onmessage = null
|
|
94
|
+
this.ws.onclose = null
|
|
95
|
+
this.ws.onerror = null
|
|
96
|
+
try {
|
|
97
|
+
this.ws.close()
|
|
98
|
+
} catch {
|
|
99
|
+
// ignore
|
|
100
|
+
}
|
|
101
|
+
this.ws = null
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private enqueue(serialized: string): void {
|
|
106
|
+
this.sendQueue.push(serialized)
|
|
107
|
+
while (this.sendQueue.length > SEND_QUEUE_MAX) {
|
|
108
|
+
this.sendQueue.shift()
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private flushQueue(): void {
|
|
113
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return
|
|
114
|
+
const pending = this.sendQueue
|
|
115
|
+
this.sendQueue = []
|
|
116
|
+
for (const item of pending) {
|
|
117
|
+
this.ws.send(item)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private scheduleReconnect(): void {
|
|
122
|
+
if (this.closed) return
|
|
123
|
+
this.clearReconnectTimer()
|
|
124
|
+
const delay = Math.min(
|
|
125
|
+
RECONNECT_MAX_MS,
|
|
126
|
+
1000 * 2 ** Math.min(this.reconnectAttempt, 5),
|
|
127
|
+
)
|
|
128
|
+
this.reconnectAttempt += 1
|
|
129
|
+
this.reconnectTimer = setTimeout(() => {
|
|
130
|
+
this.reconnectTimer = null
|
|
131
|
+
this.connect()
|
|
132
|
+
}, delay)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private clearReconnectTimer(): void {
|
|
136
|
+
if (this.reconnectTimer != null) {
|
|
137
|
+
clearTimeout(this.reconnectTimer)
|
|
138
|
+
this.reconnectTimer = null
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private handleMessage(raw: unknown): void {
|
|
143
|
+
if (typeof raw !== 'string') return
|
|
144
|
+
let parsed: unknown
|
|
145
|
+
try {
|
|
146
|
+
parsed = JSON.parse(raw)
|
|
147
|
+
} catch {
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return
|
|
151
|
+
const envelope = parsed as FormRuntimeEnvelope<FormAutoControlEventData>
|
|
152
|
+
if (envelope.type !== 'form_auto_control_event') return
|
|
153
|
+
if (String(envelope.ask_user_message_id ?? '') !== this.askUserMessageId) return
|
|
154
|
+
const data = envelope.data
|
|
155
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) return
|
|
156
|
+
this.onAutoControl?.(data, envelope)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type FormRuntimeAction = 'focus' | 'change' | 'select' | 'click' | 'submit'
|
|
2
|
+
|
|
3
|
+
export interface FormRuntimeEventData {
|
|
4
|
+
action: FormRuntimeAction
|
|
5
|
+
component: string
|
|
6
|
+
field?: string
|
|
7
|
+
value?: unknown
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface FormAutoControlEventData {
|
|
11
|
+
action: FormRuntimeAction
|
|
12
|
+
field?: string
|
|
13
|
+
value?: unknown
|
|
14
|
+
component?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type FormRuntimeOutboundType =
|
|
18
|
+
| 'form_runtime_data'
|
|
19
|
+
| 'form_runtime_event'
|
|
20
|
+
| 'form_auto_control_bac_event'
|
|
21
|
+
|
|
22
|
+
export interface FormRuntimeEnvelope<T = Record<string, unknown>> {
|
|
23
|
+
type: string
|
|
24
|
+
ask_user_message_id: string
|
|
25
|
+
data: T
|
|
26
|
+
}
|