@skopon-cool/form-sdk 0.2.4 → 0.2.5
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/client/authStorage.d.ts +6 -1
- package/dist/client/authStorage.d.ts.map +1 -1
- package/dist/components/AskUserFormCard.d.ts +3 -1
- package/dist/components/AskUserFormCard.d.ts.map +1 -1
- package/dist/components/CurlSubmitBlock.d.ts +3 -1
- package/dist/components/CurlSubmitBlock.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +958 -919
- package/dist/submit/buildCurlStatement.d.ts +4 -3
- package/dist/submit/buildCurlStatement.d.ts.map +1 -1
- package/dist/submit/submitAnswerAgent.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/authStorage.test.ts +40 -1
- package/src/client/authStorage.ts +18 -4
- package/src/components/AskUserFormCard.tsx +16 -2
- package/src/components/CurlSubmitBlock.tsx +14 -3
- package/src/index.ts +1 -0
- package/src/submit/buildCurlStatement.ts +20 -3
- package/src/submit/submit.test.ts +81 -0
- package/src/submit/submitAnswerAgent.ts +22 -13
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
export declare function buildCurlStatement(payload: unknown, callbackUrl?: string | null): string;
|
|
1
|
+
export declare function buildCurlStatement(payload: unknown, callbackUrl?: string | null, headers?: Record<string, string>): string;
|
|
2
2
|
/** 构建合法 JSON body(card + extra 合并,便于 curl 直接执行) */
|
|
3
3
|
export declare function buildAskUserCurlBodyJson(cardValues: Record<string, unknown>, extraValues: Record<string, unknown>): string;
|
|
4
4
|
export interface BuildAskUserCurlStatementOptions {
|
|
5
5
|
cardValues: Record<string, unknown>;
|
|
6
6
|
extraValues?: Record<string, unknown>;
|
|
7
7
|
callbackUrl?: string | null;
|
|
8
|
+
headers?: Record<string, string>;
|
|
8
9
|
}
|
|
9
|
-
export declare function buildAskUserCurlStatement({ cardValues, extraValues, callbackUrl, }: BuildAskUserCurlStatementOptions): string;
|
|
10
|
+
export declare function buildAskUserCurlStatement({ cardValues, extraValues, callbackUrl, headers, }: BuildAskUserCurlStatementOptions): string;
|
|
10
11
|
/** 从 ask_user payload(blocksJson)生成向 callback 提交返回字段的 curl */
|
|
11
|
-
export declare function buildAskUserSubmitCurlFromPayload(payload: unknown, callbackUrl?: string | null): string;
|
|
12
|
+
export declare function buildAskUserSubmitCurlFromPayload(payload: unknown, callbackUrl?: string | null, headers?: Record<string, string>): string;
|
|
12
13
|
//# sourceMappingURL=buildCurlStatement.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildCurlStatement.d.ts","sourceRoot":"","sources":["../../src/submit/buildCurlStatement.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"buildCurlStatement.d.ts","sourceRoot":"","sources":["../../src/submit/buildCurlStatement.ts"],"names":[],"mappings":"AAiBA,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,OAAO,EAChB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,EAC3B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,MAAM,CASR;AAED,mDAAmD;AACnD,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,MAAM,CAER;AAED,MAAM,WAAW,gCAAgC;IAC/C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACrC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACjC;AAED,wBAAgB,yBAAyB,CAAC,EACxC,UAAU,EACV,WAAgB,EAChB,WAAW,EACX,OAAO,GACR,EAAE,gCAAgC,GAAG,MAAM,CAS3C;AAED,8DAA8D;AAC9D,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,OAAO,EAChB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,EAC3B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,MAAM,CAKR"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"submitAnswerAgent.d.ts","sourceRoot":"","sources":["../../src/submit/submitAnswerAgent.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"submitAnswerAgent.d.ts","sourceRoot":"","sources":["../../src/submit/submitAnswerAgent.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,wBAAwB,EAC9B,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EAC1B,MAAM,kBAAkB,CAAA;AAEzB,MAAM,WAAW,wBAAyB,SAAQ,qBAAqB;IACrE,WAAW,CAAC,EAAE,wBAAwB,CAAA;IACtC,kDAAkD;IAClD,sBAAsB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACvC;AAwDD,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,oBAAoB,GAC3B;IAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAchD;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,oBAAoB,CAAC,CAmB/B"}
|
package/package.json
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
DEFAULT_AUTH_STORAGE_KEY,
|
|
9
9
|
resolveAnswerAgentCallbackUrl,
|
|
10
10
|
resolveSkoponAuthHeaders,
|
|
11
|
+
resolveSubmitAuthHeaders,
|
|
11
12
|
} from './authStorage'
|
|
12
13
|
|
|
13
14
|
describe('resolveSkoponAuthHeaders', () => {
|
|
@@ -63,6 +64,33 @@ describe('resolveSkoponAuthHeaders', () => {
|
|
|
63
64
|
})
|
|
64
65
|
})
|
|
65
66
|
|
|
67
|
+
describe('resolveSubmitAuthHeaders', () => {
|
|
68
|
+
afterEach(() => {
|
|
69
|
+
sessionStorage.clear()
|
|
70
|
+
localStorage.clear()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('merges session auth with explicit headers, explicit wins', () => {
|
|
74
|
+
sessionStorage.setItem(
|
|
75
|
+
DEFAULT_AUTH_STORAGE_KEY,
|
|
76
|
+
JSON.stringify({ token: 'session-tok' }),
|
|
77
|
+
)
|
|
78
|
+
expect(
|
|
79
|
+
resolveSubmitAuthHeaders({ Authorization: 'Bearer explicit-tok' }),
|
|
80
|
+
).toEqual({ Authorization: 'Bearer explicit-tok' })
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('falls back to session auth when explicit headers missing', () => {
|
|
84
|
+
sessionStorage.setItem(
|
|
85
|
+
DEFAULT_AUTH_STORAGE_KEY,
|
|
86
|
+
JSON.stringify({ token: 'session-tok' }),
|
|
87
|
+
)
|
|
88
|
+
expect(resolveSubmitAuthHeaders()).toEqual({
|
|
89
|
+
Authorization: 'Bearer session-tok',
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
66
94
|
describe('resolveAnswerAgentCallbackUrl', () => {
|
|
67
95
|
afterEach(() => {
|
|
68
96
|
sessionStorage.clear()
|
|
@@ -86,7 +114,18 @@ describe('resolveAnswerAgentCallbackUrl', () => {
|
|
|
86
114
|
expect(resolveAnswerAgentCallbackUrl()).toBe(DEFAULT_ANSWER_AGENT_CALLBACK_URL)
|
|
87
115
|
})
|
|
88
116
|
|
|
89
|
-
it('returns
|
|
117
|
+
it('returns empty string when session is missing', () => {
|
|
118
|
+
expect(resolveAnswerAgentCallbackUrl()).toBe('')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('returns default mock url when field is empty string', () => {
|
|
122
|
+
sessionStorage.setItem(
|
|
123
|
+
DEFAULT_AUTH_STORAGE_KEY,
|
|
124
|
+
JSON.stringify({
|
|
125
|
+
token: 'tok',
|
|
126
|
+
[DEFAULT_AUTH_ANSWER_AGENT_CALLBACK_URL_FIELD]: ' ',
|
|
127
|
+
}),
|
|
128
|
+
)
|
|
90
129
|
expect(resolveAnswerAgentCallbackUrl()).toBe(DEFAULT_ANSWER_AGENT_CALLBACK_URL)
|
|
91
130
|
})
|
|
92
131
|
})
|
|
@@ -46,7 +46,10 @@ function readTokenFromSessionJson(
|
|
|
46
46
|
: ''
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* 从宿主登录缓存读取 answer_agent_callback_url。
|
|
51
|
+
* 无 session / 无法读取时返回空串;session 存在但字段缺失或为空时回落到默认 mock 路径(与 skopon-admin 登录迁移一致)。
|
|
52
|
+
*/
|
|
50
53
|
export function resolveAnswerAgentCallbackUrl(
|
|
51
54
|
options: SkoponAuthStorageOptions & {
|
|
52
55
|
answerAgentCallbackUrlField?: string
|
|
@@ -58,19 +61,30 @@ export function resolveAnswerAgentCallbackUrl(
|
|
|
58
61
|
options.answerAgentCallbackUrlField ?? DEFAULT_AUTH_ANSWER_AGENT_CALLBACK_URL_FIELD
|
|
59
62
|
|
|
60
63
|
const storage = readStorage(storageKind)
|
|
61
|
-
if (!storage) return
|
|
64
|
+
if (!storage) return ''
|
|
62
65
|
|
|
63
66
|
const raw = storage.getItem(storageKey)
|
|
64
|
-
if (!raw) return
|
|
67
|
+
if (!raw) return ''
|
|
65
68
|
|
|
66
69
|
const parsed = readSessionJson(raw)
|
|
67
|
-
if (!parsed) return
|
|
70
|
+
if (!parsed) return ''
|
|
68
71
|
|
|
69
72
|
const url = parsed[field]
|
|
70
73
|
const trimmed = typeof url === 'string' ? url.trim() : ''
|
|
71
74
|
return trimmed || DEFAULT_ANSWER_AGENT_CALLBACK_URL
|
|
72
75
|
}
|
|
73
76
|
|
|
77
|
+
/** 合并 session 鉴权头与宿主显式 headers;显式 headers 优先。 */
|
|
78
|
+
export function resolveSubmitAuthHeaders(
|
|
79
|
+
explicit?: Record<string, string>,
|
|
80
|
+
authStorage?: SkoponAuthStorageOptions,
|
|
81
|
+
): Record<string, string> {
|
|
82
|
+
return {
|
|
83
|
+
...resolveSkoponAuthHeaders(authStorage),
|
|
84
|
+
...(explicit ?? {}),
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
74
88
|
/** 从宿主登录缓存读取 Bearer token,返回 Authorization 头(无 token 时返回空对象)。 */
|
|
75
89
|
export function resolveSkoponAuthHeaders(
|
|
76
90
|
options: SkoponAuthStorageOptions = {},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
2
2
|
import { Button, Spin, Tag, Tooltip } from 'antd'
|
|
3
3
|
import type { FormDetailResult, FormSchema, SubmitMode } from '../types/index'
|
|
4
|
+
import { resolveSubmitAuthHeaders } from '../client/authStorage'
|
|
4
5
|
import { buildAskUserSurface } from '../adapter/resolveSurface'
|
|
5
6
|
import {
|
|
6
7
|
buildAnswerAgentBody,
|
|
@@ -46,6 +47,8 @@ export interface AskUserFormCardProps {
|
|
|
46
47
|
callbackUrl?: string | null
|
|
47
48
|
submitMode?: SubmitMode
|
|
48
49
|
answerAgentContext?: AskUserAnswerAgentContext
|
|
50
|
+
/** 显式提交 headers,与 session 鉴权合并;显式优先 */
|
|
51
|
+
submitHeaders?: Record<string, string>
|
|
49
52
|
/** 需 useCallback 稳定引用,避免重复拉取 */
|
|
50
53
|
fetchFormDetail: (ref: { formUniqueId: string }) => Promise<FormDetailResult>
|
|
51
54
|
onNotify?: (type: 'success' | 'error', message: string) => void
|
|
@@ -61,6 +64,7 @@ export default function AskUserFormCard({
|
|
|
61
64
|
callbackUrl,
|
|
62
65
|
submitMode = 'curl',
|
|
63
66
|
answerAgentContext,
|
|
67
|
+
submitHeaders,
|
|
64
68
|
fetchFormDetail,
|
|
65
69
|
onNotify,
|
|
66
70
|
resumeSearch = null,
|
|
@@ -77,6 +81,11 @@ export default function AskUserFormCard({
|
|
|
77
81
|
|
|
78
82
|
const resolvedFormUniqueId = formUniqueId?.trim() ?? ''
|
|
79
83
|
|
|
84
|
+
const resolvedSubmitHeaders = useMemo(
|
|
85
|
+
() => resolveSubmitAuthHeaders(submitHeaders),
|
|
86
|
+
[submitHeaders],
|
|
87
|
+
)
|
|
88
|
+
|
|
80
89
|
const resolvedAnswerAgentContext = useMemo(
|
|
81
90
|
(): AskUserAnswerAgentContext => ({
|
|
82
91
|
...answerAgentContext,
|
|
@@ -188,6 +197,7 @@ export default function AskUserFormCard({
|
|
|
188
197
|
payload={payload}
|
|
189
198
|
callbackUrl={callbackUrl}
|
|
190
199
|
answerAgentContext={resolvedAnswerAgentContext}
|
|
200
|
+
submitHeaders={submitHeaders}
|
|
191
201
|
unpublishedFormId={formDisabled && resolvedFormUniqueId ? resolvedFormUniqueId : undefined}
|
|
192
202
|
onNotify={onNotify}
|
|
193
203
|
/>
|
|
@@ -213,7 +223,9 @@ export default function AskUserFormCard({
|
|
|
213
223
|
onNotifyRef.current?.('error', 'callback_url 为空,无法提交')
|
|
214
224
|
return
|
|
215
225
|
}
|
|
216
|
-
const result = await submitFormJson(url, answer
|
|
226
|
+
const result = await submitFormJson(url, answer, {
|
|
227
|
+
headers: resolvedSubmitHeaders,
|
|
228
|
+
})
|
|
217
229
|
if (!mountedRef.current) return
|
|
218
230
|
if (result.ok) {
|
|
219
231
|
onNotifyRef.current?.('success', '提交成功')
|
|
@@ -229,7 +241,9 @@ export default function AskUserFormCard({
|
|
|
229
241
|
form: formDef ?? payloadDef,
|
|
230
242
|
answer,
|
|
231
243
|
})
|
|
232
|
-
const result = await submitAnswerAgent(body
|
|
244
|
+
const result = await submitAnswerAgent(body, {
|
|
245
|
+
headers: resolvedSubmitHeaders,
|
|
246
|
+
})
|
|
233
247
|
if (!mountedRef.current) return
|
|
234
248
|
const notify = formatAnswerAgentSubmitMessage(result)
|
|
235
249
|
onNotifyRef.current?.(notify.type, notify.message)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
2
2
|
import { Button, Tag, Tooltip, Typography } from 'antd'
|
|
3
|
+
import { resolveSubmitAuthHeaders } from '../client/authStorage'
|
|
3
4
|
import { buildCurlStatement } from '../submit/buildCurlStatement'
|
|
4
5
|
import {
|
|
5
6
|
buildAnswerAgentBody,
|
|
@@ -25,6 +26,8 @@ export interface CurlSubmitBlockProps {
|
|
|
25
26
|
/** display:复制与显示一致;submitFields:复制向 callback 提交返回字段的 curl */
|
|
26
27
|
copyMode?: CurlSubmitBlockCopyMode
|
|
27
28
|
answerAgentContext?: AskUserAnswerAgentContext
|
|
29
|
+
/** 显式提交 headers,与 session 鉴权合并;显式优先 */
|
|
30
|
+
submitHeaders?: Record<string, string>
|
|
28
31
|
onNotify?: (type: 'success' | 'error', message: string) => void
|
|
29
32
|
}
|
|
30
33
|
|
|
@@ -44,6 +47,7 @@ export default function CurlSubmitBlock({
|
|
|
44
47
|
unpublishedFormId,
|
|
45
48
|
incompleteFormId,
|
|
46
49
|
answerAgentContext,
|
|
50
|
+
submitHeaders,
|
|
47
51
|
onNotify,
|
|
48
52
|
}: CurlSubmitBlockProps) {
|
|
49
53
|
const [submitting, setSubmitting] = useState(false)
|
|
@@ -56,9 +60,14 @@ export default function CurlSubmitBlock({
|
|
|
56
60
|
}
|
|
57
61
|
}, [])
|
|
58
62
|
|
|
63
|
+
const resolvedSubmitHeaders = useMemo(
|
|
64
|
+
() => resolveSubmitAuthHeaders(submitHeaders),
|
|
65
|
+
[submitHeaders],
|
|
66
|
+
)
|
|
67
|
+
|
|
59
68
|
const displayCurl = useMemo(
|
|
60
|
-
() => buildCurlStatement(payload, callbackUrl),
|
|
61
|
-
[payload, callbackUrl],
|
|
69
|
+
() => buildCurlStatement(payload, callbackUrl, resolvedSubmitHeaders),
|
|
70
|
+
[payload, callbackUrl, resolvedSubmitHeaders],
|
|
62
71
|
)
|
|
63
72
|
|
|
64
73
|
const resolvedContext = useMemo(
|
|
@@ -79,7 +88,9 @@ export default function CurlSubmitBlock({
|
|
|
79
88
|
payload,
|
|
80
89
|
answer,
|
|
81
90
|
})
|
|
82
|
-
const result = await submitAnswerAgent(body
|
|
91
|
+
const result = await submitAnswerAgent(body, {
|
|
92
|
+
headers: resolvedSubmitHeaders,
|
|
93
|
+
})
|
|
83
94
|
if (!mountedRef.current) return
|
|
84
95
|
const notify = formatAnswerAgentSubmitMessage(result)
|
|
85
96
|
onNotify?.(notify.type, notify.message)
|
package/src/index.ts
CHANGED
|
@@ -7,12 +7,25 @@ function escapeSingleQuotes(text: string): string {
|
|
|
7
7
|
return text.replace(/'/g, `'\\''`)
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
function resolveAuthorizationCurlHeader(headers?: Record<string, string>): string {
|
|
11
|
+
const authorization = headers?.Authorization?.trim()
|
|
12
|
+
if (authorization) {
|
|
13
|
+
return ` -H 'Authorization: ${escapeSingleQuotes(authorization)}' \\`
|
|
14
|
+
}
|
|
15
|
+
return ` -H 'Authorization: Bearer <token>' \\`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function buildCurlStatement(
|
|
19
|
+
payload: unknown,
|
|
20
|
+
callbackUrl?: string | null,
|
|
21
|
+
headers?: Record<string, string>,
|
|
22
|
+
): string {
|
|
11
23
|
const url = (callbackUrl ?? '').trim() || '<callback_url>'
|
|
12
24
|
const body = JSON.stringify(payload ?? {}, null, 2)
|
|
13
25
|
return [
|
|
14
26
|
`curl -X POST '${url}' \\`,
|
|
15
27
|
` -H 'Content-Type: application/json' \\`,
|
|
28
|
+
resolveAuthorizationCurlHeader(headers),
|
|
16
29
|
` -d '${escapeSingleQuotes(body)}'`,
|
|
17
30
|
].join('\n')
|
|
18
31
|
}
|
|
@@ -29,18 +42,21 @@ export interface BuildAskUserCurlStatementOptions {
|
|
|
29
42
|
cardValues: Record<string, unknown>
|
|
30
43
|
extraValues?: Record<string, unknown>
|
|
31
44
|
callbackUrl?: string | null
|
|
45
|
+
headers?: Record<string, string>
|
|
32
46
|
}
|
|
33
47
|
|
|
34
48
|
export function buildAskUserCurlStatement({
|
|
35
49
|
cardValues,
|
|
36
50
|
extraValues = {},
|
|
37
51
|
callbackUrl,
|
|
52
|
+
headers,
|
|
38
53
|
}: BuildAskUserCurlStatementOptions): string {
|
|
39
54
|
const url = (callbackUrl ?? '').trim() || '<callback_url>'
|
|
40
55
|
const body = buildAskUserCurlBodyJson(cardValues, extraValues)
|
|
41
56
|
return [
|
|
42
57
|
`curl -X POST '${url}' \\`,
|
|
43
58
|
` -H 'Content-Type: application/json' \\`,
|
|
59
|
+
resolveAuthorizationCurlHeader(headers),
|
|
44
60
|
` -d '${escapeSingleQuotes(body)}'`,
|
|
45
61
|
].join('\n')
|
|
46
62
|
}
|
|
@@ -49,9 +65,10 @@ export function buildAskUserCurlStatement({
|
|
|
49
65
|
export function buildAskUserSubmitCurlFromPayload(
|
|
50
66
|
payload: unknown,
|
|
51
67
|
callbackUrl?: string | null,
|
|
68
|
+
headers?: Record<string, string>,
|
|
52
69
|
): string {
|
|
53
70
|
const def = parsePayloadBlocksJson(payload)
|
|
54
|
-
if (!def) return buildCurlStatement(payload, callbackUrl)
|
|
71
|
+
if (!def) return buildCurlStatement(payload, callbackUrl, headers)
|
|
55
72
|
const cardValues = extractExtraBlockValues(def.blocks)
|
|
56
|
-
return buildAskUserCurlStatement({ cardValues, callbackUrl })
|
|
73
|
+
return buildAskUserCurlStatement({ cardValues, callbackUrl, headers })
|
|
57
74
|
}
|
|
@@ -30,6 +30,16 @@ describe('buildCurlStatement', () => {
|
|
|
30
30
|
const curl = buildCurlStatement({ name: "O'Brien" }, 'https://example.com/hook')
|
|
31
31
|
expect(curl).toContain("curl -X POST 'https://example.com/hook'")
|
|
32
32
|
expect(curl).toContain("O'\\''Brien")
|
|
33
|
+
expect(curl).toContain("Authorization: Bearer <token>")
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('includes Authorization header when provided', () => {
|
|
37
|
+
const curl = buildCurlStatement(
|
|
38
|
+
{ a: 1 },
|
|
39
|
+
'https://example.com/hook',
|
|
40
|
+
{ Authorization: 'Bearer tok-1' },
|
|
41
|
+
)
|
|
42
|
+
expect(curl).toContain("Authorization: Bearer tok-1")
|
|
33
43
|
})
|
|
34
44
|
|
|
35
45
|
it('uses placeholder when callback url missing', () => {
|
|
@@ -90,6 +100,21 @@ describe('buildAskUserSubmitCurlFromPayload', () => {
|
|
|
90
100
|
buildCurlStatement(payload, callbackUrl),
|
|
91
101
|
)
|
|
92
102
|
})
|
|
103
|
+
|
|
104
|
+
it('includes Authorization header in submit-fields curl', () => {
|
|
105
|
+
const payload = {
|
|
106
|
+
title: 'Ask',
|
|
107
|
+
blocks: [
|
|
108
|
+
{ id: 'p1', type: 'text', name: 'video_title', label: '标题', defaultValue: 'hello' },
|
|
109
|
+
],
|
|
110
|
+
}
|
|
111
|
+
const curl = buildAskUserSubmitCurlFromPayload(
|
|
112
|
+
payload,
|
|
113
|
+
'https://example.com/hook',
|
|
114
|
+
{ Authorization: 'Bearer tok-1' },
|
|
115
|
+
)
|
|
116
|
+
expect(curl).toContain("Authorization: Bearer tok-1")
|
|
117
|
+
})
|
|
93
118
|
})
|
|
94
119
|
|
|
95
120
|
describe('parsePayloadBlocksJson', () => {
|
|
@@ -409,6 +434,35 @@ describe('submitAnswerAgent', () => {
|
|
|
409
434
|
'callback_url 为空,无法提交',
|
|
410
435
|
)
|
|
411
436
|
})
|
|
437
|
+
|
|
438
|
+
it('throws when explicit answer agent callback url is empty', async () => {
|
|
439
|
+
await expect(
|
|
440
|
+
submitAnswerAgent(
|
|
441
|
+
{
|
|
442
|
+
mission_id: 1,
|
|
443
|
+
tenant_id: 1001,
|
|
444
|
+
tenant_user_id: 'u1',
|
|
445
|
+
request_id: 'req-1',
|
|
446
|
+
callback_url: 'https://example.com/cb',
|
|
447
|
+
answer: {},
|
|
448
|
+
},
|
|
449
|
+
{ answerAgentCallbackUrl: ' ' },
|
|
450
|
+
),
|
|
451
|
+
).rejects.toThrow('answer_agent_callback_url 未配置,无法提交')
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
it('throws when answer agent callback url cannot be resolved', async () => {
|
|
455
|
+
await expect(
|
|
456
|
+
submitAnswerAgent({
|
|
457
|
+
mission_id: 1,
|
|
458
|
+
tenant_id: 1001,
|
|
459
|
+
tenant_user_id: 'u1',
|
|
460
|
+
request_id: 'req-1',
|
|
461
|
+
callback_url: 'https://example.com/cb',
|
|
462
|
+
answer: {},
|
|
463
|
+
}),
|
|
464
|
+
).rejects.toThrow('answer_agent_callback_url 未配置,无法提交')
|
|
465
|
+
})
|
|
412
466
|
})
|
|
413
467
|
|
|
414
468
|
describe('submitFormJson', () => {
|
|
@@ -434,6 +488,33 @@ describe('submitFormJson', () => {
|
|
|
434
488
|
}),
|
|
435
489
|
)
|
|
436
490
|
})
|
|
491
|
+
|
|
492
|
+
it('POSTs JSON payload with Authorization header', async () => {
|
|
493
|
+
const fetchMock = vi.fn(async () => ({
|
|
494
|
+
ok: true,
|
|
495
|
+
status: 200,
|
|
496
|
+
headers: { get: () => 'application/json' },
|
|
497
|
+
json: async () => ({ ok: true }),
|
|
498
|
+
})) as unknown as typeof fetch
|
|
499
|
+
|
|
500
|
+
await submitFormJson(
|
|
501
|
+
'https://example.com/hook',
|
|
502
|
+
{ a: 1 },
|
|
503
|
+
{
|
|
504
|
+
fetch: fetchMock,
|
|
505
|
+
headers: { Authorization: 'Bearer tok-1' },
|
|
506
|
+
},
|
|
507
|
+
)
|
|
508
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
509
|
+
'https://example.com/hook',
|
|
510
|
+
expect.objectContaining({
|
|
511
|
+
headers: expect.objectContaining({
|
|
512
|
+
Authorization: 'Bearer tok-1',
|
|
513
|
+
'Content-Type': 'application/json',
|
|
514
|
+
}),
|
|
515
|
+
}),
|
|
516
|
+
)
|
|
517
|
+
})
|
|
437
518
|
})
|
|
438
519
|
|
|
439
520
|
describe('validateFormSubmitValues', () => {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
DEFAULT_ANSWER_AGENT_CALLBACK_URL,
|
|
3
2
|
resolveAnswerAgentCallbackUrl,
|
|
4
|
-
|
|
3
|
+
resolveSubmitAuthHeaders,
|
|
5
4
|
type SkoponAuthStorageOptions,
|
|
6
5
|
} from '../client/authStorage'
|
|
7
6
|
import { validateAnswerAgentBody } from './buildAnswerAgentBody'
|
|
@@ -17,6 +16,25 @@ export interface SubmitAnswerAgentOptions extends SubmitFormJsonOptions {
|
|
|
17
16
|
answerAgentCallbackUrl?: string | null
|
|
18
17
|
}
|
|
19
18
|
|
|
19
|
+
const ANSWER_AGENT_CALLBACK_URL_MISSING_ERROR =
|
|
20
|
+
'answer_agent_callback_url 未配置,无法提交'
|
|
21
|
+
|
|
22
|
+
function resolveSubmitAnswerAgentUrl(options?: SubmitAnswerAgentOptions): string {
|
|
23
|
+
if (options?.answerAgentCallbackUrl != null) {
|
|
24
|
+
const explicit = options.answerAgentCallbackUrl.trim()
|
|
25
|
+
if (!explicit) {
|
|
26
|
+
throw new Error(ANSWER_AGENT_CALLBACK_URL_MISSING_ERROR)
|
|
27
|
+
}
|
|
28
|
+
return explicit
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const resolved = resolveAnswerAgentCallbackUrl(options?.authStorage).trim()
|
|
32
|
+
if (!resolved) {
|
|
33
|
+
throw new Error(ANSWER_AGENT_CALLBACK_URL_MISSING_ERROR)
|
|
34
|
+
}
|
|
35
|
+
return resolved
|
|
36
|
+
}
|
|
37
|
+
|
|
20
38
|
interface AnswerAgentResponseData {
|
|
21
39
|
callback_ok?: boolean
|
|
22
40
|
callback_status?: number
|
|
@@ -79,20 +97,11 @@ export async function submitAnswerAgent(
|
|
|
79
97
|
throw new Error(validationError)
|
|
80
98
|
}
|
|
81
99
|
|
|
82
|
-
const url = (
|
|
83
|
-
options?.answerAgentCallbackUrl?.trim() ||
|
|
84
|
-
resolveAnswerAgentCallbackUrl(options?.authStorage) ||
|
|
85
|
-
DEFAULT_ANSWER_AGENT_CALLBACK_URL
|
|
86
|
-
).trim()
|
|
87
|
-
|
|
88
|
-
const storageHeaders = resolveSkoponAuthHeaders(options?.authStorage)
|
|
100
|
+
const url = resolveSubmitAnswerAgentUrl(options)
|
|
89
101
|
|
|
90
102
|
const result = await submitFormJson(url, body, {
|
|
91
103
|
...options,
|
|
92
|
-
headers:
|
|
93
|
-
...storageHeaders,
|
|
94
|
-
...(options?.headers ?? {}),
|
|
95
|
-
},
|
|
104
|
+
headers: resolveSubmitAuthHeaders(options?.headers, options?.authStorage),
|
|
96
105
|
})
|
|
97
106
|
|
|
98
107
|
const data = extractAnswerAgentData(result.body)
|