@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.
Files changed (37) hide show
  1. package/dist/catalog/a2uiCustomCatalog.d.ts.map +1 -1
  2. package/dist/catalog/skoponCaseSelect.d.ts.map +1 -1
  3. package/dist/catalog/skoponResumeSelect.d.ts.map +1 -1
  4. package/dist/catalog/useSkoponBoundField.d.ts +16 -1
  5. package/dist/catalog/useSkoponBoundField.d.ts.map +1 -1
  6. package/dist/client/authStorage.d.ts +8 -0
  7. package/dist/client/authStorage.d.ts.map +1 -1
  8. package/dist/components/AskUserFormCard.d.ts +6 -1
  9. package/dist/components/AskUserFormCard.d.ts.map +1 -1
  10. package/dist/components/SkoponFormRenderer.d.ts +1 -0
  11. package/dist/components/SkoponFormRenderer.d.ts.map +1 -1
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +1520 -1081
  15. package/dist/runtime/formAutoControl.d.ts +16 -0
  16. package/dist/runtime/formAutoControl.d.ts.map +1 -0
  17. package/dist/runtime/formRuntimeContext.d.ts +21 -0
  18. package/dist/runtime/formRuntimeContext.d.ts.map +1 -0
  19. package/dist/runtime/formRuntimeWs.d.ts +27 -0
  20. package/dist/runtime/formRuntimeWs.d.ts.map +1 -0
  21. package/dist/runtime/types.d.ts +20 -0
  22. package/dist/runtime/types.d.ts.map +1 -0
  23. package/package.json +1 -1
  24. package/src/catalog/a2uiCustomCatalog.tsx +41 -16
  25. package/src/catalog/skoponCaseSelect.tsx +10 -4
  26. package/src/catalog/skoponResumeSelect.tsx +10 -4
  27. package/src/catalog/useSkoponBoundField.ts +100 -4
  28. package/src/client/authStorage.test.ts +34 -0
  29. package/src/client/authStorage.ts +27 -0
  30. package/src/components/AskUserFormCard.tsx +164 -31
  31. package/src/components/SkoponFormRenderer.tsx +12 -0
  32. package/src/index.ts +2 -0
  33. package/src/runtime/formAutoControl.test.ts +127 -0
  34. package/src/runtime/formAutoControl.ts +110 -0
  35. package/src/runtime/formRuntimeContext.tsx +129 -0
  36. package/src/runtime/formRuntimeWs.ts +158 -0
  37. 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
+ }