@skopon-cool/form-sdk 0.2.3 → 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 +8 -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 +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1098 -1007
- package/dist/submit/buildAnswerAgentBody.d.ts +4 -0
- package/dist/submit/buildAnswerAgentBody.d.ts.map +1 -1
- package/dist/submit/buildCurlStatement.d.ts +4 -3
- package/dist/submit/buildCurlStatement.d.ts.map +1 -1
- package/dist/submit/submitAnswerAgent.d.ts +4 -0
- package/dist/submit/submitAnswerAgent.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/authStorage.test.ts +45 -1
- package/src/client/authStorage.ts +19 -2
- package/src/components/AskUserFormCard.tsx +22 -8
- package/src/components/CurlSubmitBlock.tsx +32 -24
- package/src/index.ts +5 -0
- package/src/submit/buildAnswerAgentBody.ts +37 -0
- package/src/submit/buildCurlStatement.ts +20 -3
- package/src/submit/submit.test.ts +163 -7
- package/src/submit/submitAnswerAgent.ts +87 -13
|
@@ -16,4 +16,8 @@ export interface BuildAnswerAgentBodyOptions {
|
|
|
16
16
|
}
|
|
17
17
|
/** 组装 answer-agent 外包消息体(与 mock AskUserAnswerMockVo 对齐)。 */
|
|
18
18
|
export declare function buildAnswerAgentBody({ context, payload, form, answer, }: BuildAnswerAgentBodyOptions): Record<string, unknown>;
|
|
19
|
+
/** 提交前校验 mock answer-agent 必填字段,返回错误文案或 null。 */
|
|
20
|
+
export declare function validateAnswerAgentBody(body: Record<string, unknown>): string | null;
|
|
21
|
+
/** 提交前校验上下文必填字段,返回错误文案或 null。 */
|
|
22
|
+
export declare function validateAnswerAgentContext(context: AskUserAnswerAgentContext): string | null;
|
|
19
23
|
//# sourceMappingURL=buildAnswerAgentBody.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildAnswerAgentBody.d.ts","sourceRoot":"","sources":["../../src/submit/buildAnswerAgentBody.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,kCAAkC,CAAA;AAEzC,MAAM,WAAW,yBAAyB;IACxC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAClC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B;AAmBD,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,yBAAyB,CAAA;IAClC,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,CAAC,EAAE,uBAAuB,GAAG,UAAU,GAAG,IAAI,CAAA;IAClD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC;AAED,4DAA4D;AAC5D,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EACP,OAAO,EACP,IAAI,EACJ,MAAM,GACP,EAAE,2BAA2B,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAevD"}
|
|
1
|
+
{"version":3,"file":"buildAnswerAgentBody.d.ts","sourceRoot":"","sources":["../../src/submit/buildAnswerAgentBody.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,kCAAkC,CAAA;AAEzC,MAAM,WAAW,yBAAyB;IACxC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAClC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B;AAmBD,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,yBAAyB,CAAA;IAClC,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,CAAC,EAAE,uBAAuB,GAAG,UAAU,GAAG,IAAI,CAAA;IAClD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC;AAED,4DAA4D;AAC5D,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EACP,OAAO,EACP,IAAI,EACJ,MAAM,GACP,EAAE,2BAA2B,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAevD;AAED,iDAAiD;AACjD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,IAAI,CAqBpF;AAED,iCAAiC;AACjC,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,yBAAyB,GACjC,MAAM,GAAG,IAAI,CAQf"}
|
|
@@ -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"}
|
|
@@ -5,5 +5,9 @@ export interface SubmitAnswerAgentOptions extends SubmitFormJsonOptions {
|
|
|
5
5
|
/** 显式覆盖 auth_user 中的 answer_agent_callback_url */
|
|
6
6
|
answerAgentCallbackUrl?: string | null;
|
|
7
7
|
}
|
|
8
|
+
export declare function formatAnswerAgentSubmitMessage(result: SubmitFormJsonResult): {
|
|
9
|
+
type: 'success' | 'error';
|
|
10
|
+
message: string;
|
|
11
|
+
};
|
|
8
12
|
export declare function submitAnswerAgent(body: Record<string, unknown>, options?: SubmitAnswerAgentOptions): Promise<SubmitFormJsonResult>;
|
|
9
13
|
//# sourceMappingURL=submitAnswerAgent.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"submitAnswerAgent.d.ts","sourceRoot":"","sources":["../../src/submit/submitAnswerAgent.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,wBAAwB,EAC9B,MAAM,uBAAuB,CAAA;
|
|
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
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_AUTH_ANSWER_AGENT_CALLBACK_URL_FIELD,
|
|
7
|
+
DEFAULT_ANSWER_AGENT_CALLBACK_URL,
|
|
7
8
|
DEFAULT_AUTH_STORAGE_KEY,
|
|
8
9
|
resolveAnswerAgentCallbackUrl,
|
|
9
10
|
resolveSkoponAuthHeaders,
|
|
11
|
+
resolveSubmitAuthHeaders,
|
|
10
12
|
} from './authStorage'
|
|
11
13
|
|
|
12
14
|
describe('resolveSkoponAuthHeaders', () => {
|
|
@@ -62,6 +64,33 @@ describe('resolveSkoponAuthHeaders', () => {
|
|
|
62
64
|
})
|
|
63
65
|
})
|
|
64
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
|
+
|
|
65
94
|
describe('resolveAnswerAgentCallbackUrl', () => {
|
|
66
95
|
afterEach(() => {
|
|
67
96
|
sessionStorage.clear()
|
|
@@ -80,8 +109,23 @@ describe('resolveAnswerAgentCallbackUrl', () => {
|
|
|
80
109
|
expect(resolveAnswerAgentCallbackUrl()).toBe('/api/v1/mock/ask_user_answer_mock')
|
|
81
110
|
})
|
|
82
111
|
|
|
83
|
-
it('returns
|
|
112
|
+
it('returns default mock url when field is missing', () => {
|
|
84
113
|
sessionStorage.setItem(DEFAULT_AUTH_STORAGE_KEY, JSON.stringify({ token: 'tok' }))
|
|
114
|
+
expect(resolveAnswerAgentCallbackUrl()).toBe(DEFAULT_ANSWER_AGENT_CALLBACK_URL)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('returns empty string when session is missing', () => {
|
|
85
118
|
expect(resolveAnswerAgentCallbackUrl()).toBe('')
|
|
86
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
|
+
)
|
|
129
|
+
expect(resolveAnswerAgentCallbackUrl()).toBe(DEFAULT_ANSWER_AGENT_CALLBACK_URL)
|
|
130
|
+
})
|
|
87
131
|
})
|
|
@@ -10,6 +10,8 @@ export interface SkoponAuthStorageOptions {
|
|
|
10
10
|
export const DEFAULT_AUTH_STORAGE_KEY = 'auth_user'
|
|
11
11
|
export const DEFAULT_AUTH_TOKEN_FIELD = 'token'
|
|
12
12
|
export const DEFAULT_AUTH_ANSWER_AGENT_CALLBACK_URL_FIELD = 'answer_agent_callback_url'
|
|
13
|
+
/** 与 skopon-admin 登录写入的 mock answer-agent 路径一致 */
|
|
14
|
+
export const DEFAULT_ANSWER_AGENT_CALLBACK_URL = '/api/v1/mock/ask_user_answer_mock'
|
|
13
15
|
|
|
14
16
|
function readStorage(
|
|
15
17
|
kind: 'sessionStorage' | 'localStorage',
|
|
@@ -44,7 +46,10 @@ function readTokenFromSessionJson(
|
|
|
44
46
|
: ''
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* 从宿主登录缓存读取 answer_agent_callback_url。
|
|
51
|
+
* 无 session / 无法读取时返回空串;session 存在但字段缺失或为空时回落到默认 mock 路径(与 skopon-admin 登录迁移一致)。
|
|
52
|
+
*/
|
|
48
53
|
export function resolveAnswerAgentCallbackUrl(
|
|
49
54
|
options: SkoponAuthStorageOptions & {
|
|
50
55
|
answerAgentCallbackUrlField?: string
|
|
@@ -65,7 +70,19 @@ export function resolveAnswerAgentCallbackUrl(
|
|
|
65
70
|
if (!parsed) return ''
|
|
66
71
|
|
|
67
72
|
const url = parsed[field]
|
|
68
|
-
|
|
73
|
+
const trimmed = typeof url === 'string' ? url.trim() : ''
|
|
74
|
+
return trimmed || DEFAULT_ANSWER_AGENT_CALLBACK_URL
|
|
75
|
+
}
|
|
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
|
+
}
|
|
69
86
|
}
|
|
70
87
|
|
|
71
88
|
/** 从宿主登录缓存读取 Bearer token,返回 Authorization 头(无 token 时返回空对象)。 */
|
|
@@ -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,
|
|
@@ -15,7 +16,10 @@ import {
|
|
|
15
16
|
payloadHasInputBlocks,
|
|
16
17
|
} from '../submit/intersectPayloadBlocksWithForm'
|
|
17
18
|
import { submitFormJson } from '../submit/submitFormJson'
|
|
18
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
formatAnswerAgentSubmitMessage,
|
|
21
|
+
submitAnswerAgent,
|
|
22
|
+
} from '../submit/submitAnswerAgent'
|
|
19
23
|
import { validateFormSubmitValues } from '../submit/validateFormSubmit'
|
|
20
24
|
import type { CaseSearchFn } from '../catalog/caseSearchContext'
|
|
21
25
|
import type { ResumeSearchFn } from '../catalog/resumeSearchContext'
|
|
@@ -43,6 +47,8 @@ export interface AskUserFormCardProps {
|
|
|
43
47
|
callbackUrl?: string | null
|
|
44
48
|
submitMode?: SubmitMode
|
|
45
49
|
answerAgentContext?: AskUserAnswerAgentContext
|
|
50
|
+
/** 显式提交 headers,与 session 鉴权合并;显式优先 */
|
|
51
|
+
submitHeaders?: Record<string, string>
|
|
46
52
|
/** 需 useCallback 稳定引用,避免重复拉取 */
|
|
47
53
|
fetchFormDetail: (ref: { formUniqueId: string }) => Promise<FormDetailResult>
|
|
48
54
|
onNotify?: (type: 'success' | 'error', message: string) => void
|
|
@@ -58,6 +64,7 @@ export default function AskUserFormCard({
|
|
|
58
64
|
callbackUrl,
|
|
59
65
|
submitMode = 'curl',
|
|
60
66
|
answerAgentContext,
|
|
67
|
+
submitHeaders,
|
|
61
68
|
fetchFormDetail,
|
|
62
69
|
onNotify,
|
|
63
70
|
resumeSearch = null,
|
|
@@ -74,6 +81,11 @@ export default function AskUserFormCard({
|
|
|
74
81
|
|
|
75
82
|
const resolvedFormUniqueId = formUniqueId?.trim() ?? ''
|
|
76
83
|
|
|
84
|
+
const resolvedSubmitHeaders = useMemo(
|
|
85
|
+
() => resolveSubmitAuthHeaders(submitHeaders),
|
|
86
|
+
[submitHeaders],
|
|
87
|
+
)
|
|
88
|
+
|
|
77
89
|
const resolvedAnswerAgentContext = useMemo(
|
|
78
90
|
(): AskUserAnswerAgentContext => ({
|
|
79
91
|
...answerAgentContext,
|
|
@@ -185,6 +197,7 @@ export default function AskUserFormCard({
|
|
|
185
197
|
payload={payload}
|
|
186
198
|
callbackUrl={callbackUrl}
|
|
187
199
|
answerAgentContext={resolvedAnswerAgentContext}
|
|
200
|
+
submitHeaders={submitHeaders}
|
|
188
201
|
unpublishedFormId={formDisabled && resolvedFormUniqueId ? resolvedFormUniqueId : undefined}
|
|
189
202
|
onNotify={onNotify}
|
|
190
203
|
/>
|
|
@@ -210,7 +223,9 @@ export default function AskUserFormCard({
|
|
|
210
223
|
onNotifyRef.current?.('error', 'callback_url 为空,无法提交')
|
|
211
224
|
return
|
|
212
225
|
}
|
|
213
|
-
const result = await submitFormJson(url, answer
|
|
226
|
+
const result = await submitFormJson(url, answer, {
|
|
227
|
+
headers: resolvedSubmitHeaders,
|
|
228
|
+
})
|
|
214
229
|
if (!mountedRef.current) return
|
|
215
230
|
if (result.ok) {
|
|
216
231
|
onNotifyRef.current?.('success', '提交成功')
|
|
@@ -226,13 +241,12 @@ export default function AskUserFormCard({
|
|
|
226
241
|
form: formDef ?? payloadDef,
|
|
227
242
|
answer,
|
|
228
243
|
})
|
|
229
|
-
const result = await submitAnswerAgent(body
|
|
244
|
+
const result = await submitAnswerAgent(body, {
|
|
245
|
+
headers: resolvedSubmitHeaders,
|
|
246
|
+
})
|
|
230
247
|
if (!mountedRef.current) return
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
} else {
|
|
234
|
-
onNotifyRef.current?.('error', formatSubmitErrorMessage(result.status, result.body))
|
|
235
|
-
}
|
|
248
|
+
const notify = formatAnswerAgentSubmitMessage(result)
|
|
249
|
+
onNotifyRef.current?.(notify.type, notify.message)
|
|
236
250
|
} catch (error) {
|
|
237
251
|
if (!mountedRef.current) return
|
|
238
252
|
const detail = error instanceof Error ? error.message : '提交失败'
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { useMemo, useState } from 'react'
|
|
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,
|
|
@@ -9,7 +10,10 @@ import {
|
|
|
9
10
|
extractExtraBlockValues,
|
|
10
11
|
parsePayloadBlocksJson,
|
|
11
12
|
} from '../submit/intersectPayloadBlocksWithForm'
|
|
12
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
formatAnswerAgentSubmitMessage,
|
|
15
|
+
submitAnswerAgent,
|
|
16
|
+
} from '../submit/submitAnswerAgent'
|
|
13
17
|
|
|
14
18
|
export type CurlSubmitBlockCopyMode = 'display' | 'submitFields'
|
|
15
19
|
|
|
@@ -22,22 +26,11 @@ export interface CurlSubmitBlockProps {
|
|
|
22
26
|
/** display:复制与显示一致;submitFields:复制向 callback 提交返回字段的 curl */
|
|
23
27
|
copyMode?: CurlSubmitBlockCopyMode
|
|
24
28
|
answerAgentContext?: AskUserAnswerAgentContext
|
|
29
|
+
/** 显式提交 headers,与 session 鉴权合并;显式优先 */
|
|
30
|
+
submitHeaders?: Record<string, string>
|
|
25
31
|
onNotify?: (type: 'success' | 'error', message: string) => void
|
|
26
32
|
}
|
|
27
33
|
|
|
28
|
-
function formatSubmitErrorMessage(status: number, body: unknown): string {
|
|
29
|
-
if (body && typeof body === 'object' && body !== null && 'message' in body) {
|
|
30
|
-
const msg = (body as { message: unknown }).message
|
|
31
|
-
if (typeof msg === 'string' && msg.trim()) {
|
|
32
|
-
return `提交失败 (${status}): ${msg.trim()}`
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
if (typeof body === 'string' && body.trim()) {
|
|
36
|
-
return `提交失败 (${status}): ${body.trim()}`
|
|
37
|
-
}
|
|
38
|
-
return `提交失败 (${status})`
|
|
39
|
-
}
|
|
40
|
-
|
|
41
34
|
function resolveAnswerFromPayload(payload: unknown): Record<string, unknown> {
|
|
42
35
|
const def = parsePayloadBlocksJson(payload)
|
|
43
36
|
if (def) return extractExtraBlockValues(def.blocks)
|
|
@@ -54,13 +47,27 @@ export default function CurlSubmitBlock({
|
|
|
54
47
|
unpublishedFormId,
|
|
55
48
|
incompleteFormId,
|
|
56
49
|
answerAgentContext,
|
|
50
|
+
submitHeaders,
|
|
57
51
|
onNotify,
|
|
58
52
|
}: CurlSubmitBlockProps) {
|
|
59
53
|
const [submitting, setSubmitting] = useState(false)
|
|
54
|
+
const mountedRef = useRef(true)
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
mountedRef.current = true
|
|
58
|
+
return () => {
|
|
59
|
+
mountedRef.current = false
|
|
60
|
+
}
|
|
61
|
+
}, [])
|
|
62
|
+
|
|
63
|
+
const resolvedSubmitHeaders = useMemo(
|
|
64
|
+
() => resolveSubmitAuthHeaders(submitHeaders),
|
|
65
|
+
[submitHeaders],
|
|
66
|
+
)
|
|
60
67
|
|
|
61
68
|
const displayCurl = useMemo(
|
|
62
|
-
() => buildCurlStatement(payload, callbackUrl),
|
|
63
|
-
[payload, callbackUrl],
|
|
69
|
+
() => buildCurlStatement(payload, callbackUrl, resolvedSubmitHeaders),
|
|
70
|
+
[payload, callbackUrl, resolvedSubmitHeaders],
|
|
64
71
|
)
|
|
65
72
|
|
|
66
73
|
const resolvedContext = useMemo(
|
|
@@ -81,17 +88,18 @@ export default function CurlSubmitBlock({
|
|
|
81
88
|
payload,
|
|
82
89
|
answer,
|
|
83
90
|
})
|
|
84
|
-
const result = await submitAnswerAgent(body
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
const result = await submitAnswerAgent(body, {
|
|
92
|
+
headers: resolvedSubmitHeaders,
|
|
93
|
+
})
|
|
94
|
+
if (!mountedRef.current) return
|
|
95
|
+
const notify = formatAnswerAgentSubmitMessage(result)
|
|
96
|
+
onNotify?.(notify.type, notify.message)
|
|
90
97
|
} catch (error) {
|
|
98
|
+
if (!mountedRef.current) return
|
|
91
99
|
const detail = error instanceof Error ? error.message : '提交失败'
|
|
92
100
|
onNotify?.('error', detail)
|
|
93
101
|
} finally {
|
|
94
|
-
setSubmitting(false)
|
|
102
|
+
if (mountedRef.current) setSubmitting(false)
|
|
95
103
|
}
|
|
96
104
|
}
|
|
97
105
|
|
package/src/index.ts
CHANGED
|
@@ -67,20 +67,25 @@ export {
|
|
|
67
67
|
export { createFormClient, type FormClient, type FormClientOptions } from './client/formClient'
|
|
68
68
|
export {
|
|
69
69
|
buildAnswerAgentBody,
|
|
70
|
+
validateAnswerAgentBody,
|
|
71
|
+
validateAnswerAgentContext,
|
|
70
72
|
type AskUserAnswerAgentContext,
|
|
71
73
|
type BuildAnswerAgentBodyOptions,
|
|
72
74
|
} from './submit/buildAnswerAgentBody'
|
|
73
75
|
export {
|
|
74
76
|
submitAnswerAgent,
|
|
77
|
+
formatAnswerAgentSubmitMessage,
|
|
75
78
|
type SubmitAnswerAgentOptions,
|
|
76
79
|
} from './submit/submitAnswerAgent'
|
|
77
80
|
export {
|
|
78
81
|
resolveAnswerAgentCallbackUrl,
|
|
79
82
|
resolveSkoponAuthHeaders,
|
|
83
|
+
resolveSubmitAuthHeaders,
|
|
80
84
|
resolveClientHeaders,
|
|
81
85
|
DEFAULT_AUTH_STORAGE_KEY,
|
|
82
86
|
DEFAULT_AUTH_TOKEN_FIELD,
|
|
83
87
|
DEFAULT_AUTH_ANSWER_AGENT_CALLBACK_URL_FIELD,
|
|
88
|
+
DEFAULT_ANSWER_AGENT_CALLBACK_URL,
|
|
84
89
|
type SkoponAuthStorageOptions,
|
|
85
90
|
type ClientHeadersOptions,
|
|
86
91
|
} from './client/authStorage'
|
|
@@ -59,3 +59,40 @@ export function buildAnswerAgentBody({
|
|
|
59
59
|
answer,
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
+
|
|
63
|
+
/** 提交前校验 mock answer-agent 必填字段,返回错误文案或 null。 */
|
|
64
|
+
export function validateAnswerAgentBody(body: Record<string, unknown>): string | null {
|
|
65
|
+
const callbackUrl = body.callback_url
|
|
66
|
+
if (typeof callbackUrl !== 'string' || !callbackUrl.trim()) {
|
|
67
|
+
return 'callback_url 为空,无法提交'
|
|
68
|
+
}
|
|
69
|
+
if (body.tenant_id == null) {
|
|
70
|
+
return 'tenant_id 缺失,无法提交'
|
|
71
|
+
}
|
|
72
|
+
const tenantUserId = body.tenant_user_id
|
|
73
|
+
if (typeof tenantUserId !== 'string' || !tenantUserId.trim()) {
|
|
74
|
+
return 'tenant_user_id 缺失,无法提交'
|
|
75
|
+
}
|
|
76
|
+
const requestId = body.request_id
|
|
77
|
+
if (typeof requestId !== 'string' || !requestId.trim()) {
|
|
78
|
+
return 'request_id 缺失,无法提交'
|
|
79
|
+
}
|
|
80
|
+
const missionId = body.mission_id
|
|
81
|
+
if (missionId == null || missionId === '') {
|
|
82
|
+
return 'mission_id 缺失,无法提交'
|
|
83
|
+
}
|
|
84
|
+
return null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** 提交前校验上下文必填字段,返回错误文案或 null。 */
|
|
88
|
+
export function validateAnswerAgentContext(
|
|
89
|
+
context: AskUserAnswerAgentContext,
|
|
90
|
+
): string | null {
|
|
91
|
+
return validateAnswerAgentBody(
|
|
92
|
+
buildAnswerAgentBody({
|
|
93
|
+
context,
|
|
94
|
+
payload: {},
|
|
95
|
+
answer: {},
|
|
96
|
+
}),
|
|
97
|
+
)
|
|
98
|
+
}
|
|
@@ -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
|
}
|