@csntgao/uni-base 0.1.0
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/.prettierignore +4 -0
- package/.prettierrc.json +6 -0
- package/AGENTS.md +519 -0
- package/CLAUDE.md +512 -0
- package/README.md +734 -0
- package/docs//346/225/260/345/255/227/345/221/230/345/267/245/345/256/214/346/225/264/346/212/275/345/261/211/346/216/245/345/205/245/350/257/264/346/230/216.md +110 -0
- package/docs//347/273/204/347/273/207/345/221/230/345/267/245/345/215/225/351/200/211/347/273/204/344/273/266/346/216/245/345/205/245/350/257/264/346/230/216.md +79 -0
- package/docs//347/273/204/347/273/207/345/221/230/345/267/245/345/244/232/351/200/211/347/273/204/344/273/266/346/216/245/345/205/245/350/257/264/346/230/216.md +93 -0
- package/docs//350/264/246/346/210/267/344/270/216/347/231/273/345/275/225/347/273/204/344/273/266/345/212/250/346/200/201/346/216/245/345/205/245/350/257/264/346/230/216.md +201 -0
- package/eslint.config.js +30 -0
- package/examples/vanilla/index.html +33 -0
- package/examples/vanilla/main.js +247 -0
- package/examples/vanilla/package.json +15 -0
- package/examples/vanilla/public/runtime-config.js +10 -0
- package/examples/vanilla/style.css +99 -0
- package/package.json +31 -0
- package/packages/core/package.json +30 -0
- package/packages/core/src/client.ts +1132 -0
- package/packages/core/src/customer-support-client.ts +825 -0
- package/packages/core/src/customer-support-types.ts +123 -0
- package/packages/core/src/index.ts +79 -0
- package/packages/core/src/notification-center-client.ts +462 -0
- package/packages/core/src/notification-center-types.ts +154 -0
- package/packages/core/src/protocol.ts +237 -0
- package/packages/core/src/types.ts +170 -0
- package/packages/core/tests/client.test.ts +760 -0
- package/packages/core/tests/customer-support-client.test.ts +576 -0
- package/packages/core/tests/fake-websocket.ts +55 -0
- package/packages/core/tests/notification-center-client.test.ts +467 -0
- package/packages/core/tests/protocol.test.ts +124 -0
- package/packages/core/tsconfig.json +7 -0
- package/packages/core/tsup.config.ts +11 -0
- package/packages/web-components/package.json +35 -0
- package/packages/web-components/scripts/verify-runtime-build.mjs +111 -0
- package/packages/web-components/src/account-context.ts +1540 -0
- package/packages/web-components/src/application-switcher-transport.ts +243 -0
- package/packages/web-components/src/application-switcher.ts +1103 -0
- package/packages/web-components/src/chat.ts +1754 -0
- package/packages/web-components/src/customer-support-chat.ts +862 -0
- package/packages/web-components/src/customer-support-transport.ts +269 -0
- package/packages/web-components/src/index.ts +256 -0
- package/packages/web-components/src/notification-center.ts +1066 -0
- package/packages/web-components/src/notification-popover.ts +615 -0
- package/packages/web-components/src/organization-multi-employee-selector.ts +1564 -0
- package/packages/web-components/src/organization-onboarding-transport.ts +54 -0
- package/packages/web-components/src/organization-onboarding.ts +1216 -0
- package/packages/web-components/src/organization-selector.ts +534 -0
- package/packages/web-components/src/organization-single-employee-selector.ts +114 -0
- package/packages/web-components/src/prototype-icons.ts +242 -0
- package/packages/web-components/src/safe-image-url.ts +29 -0
- package/packages/web-components/src/safe-markdown.ts +192 -0
- package/packages/web-components/src/uniplat-base-account-context-transport.ts +625 -0
- package/packages/web-components/src/uniplat-base-application-switcher-transport.ts +418 -0
- package/packages/web-components/src/uniplat-base-application-ticket.ts +254 -0
- package/packages/web-components/src/uniplat-base-identity-ticket.ts +202 -0
- package/packages/web-components/src/uniplat-base-notification-center-transport.ts +378 -0
- package/packages/web-components/src/uniplat-base-organization-multi-employee-selector-transport.ts +394 -0
- package/packages/web-components/src/uniplat-base-organization-onboarding-transport.ts +183 -0
- package/packages/web-components/src/uniplat-base-organization-single-employee-selector-transport.ts +14 -0
- package/packages/web-components/src/user-login-transport.ts +563 -0
- package/packages/web-components/src/user-login.ts +2027 -0
- package/packages/web-components/src/user-menu.ts +880 -0
- package/packages/web-components/tests/account-context.test.ts +798 -0
- package/packages/web-components/tests/application-switcher.test.ts +562 -0
- package/packages/web-components/tests/chat.test.ts +1130 -0
- package/packages/web-components/tests/customer-support-chat.test.ts +428 -0
- package/packages/web-components/tests/customer-support-transport.test.ts +79 -0
- package/packages/web-components/tests/notification-center-transport.test.ts +271 -0
- package/packages/web-components/tests/notification-center.test.ts +429 -0
- package/packages/web-components/tests/notification-popover.test.ts +283 -0
- package/packages/web-components/tests/organization-multi-employee-selector-transport.test.ts +332 -0
- package/packages/web-components/tests/organization-multi-employee-selector.test.ts +225 -0
- package/packages/web-components/tests/organization-onboarding.test.ts +260 -0
- package/packages/web-components/tests/organization-selector.test.ts +127 -0
- package/packages/web-components/tests/organization-single-employee-selector.test.ts +145 -0
- package/packages/web-components/tests/uniplat-base-account-context-transport.test.ts +496 -0
- package/packages/web-components/tests/uniplat-base-application-switcher-transport.test.ts +598 -0
- package/packages/web-components/tests/uniplat-base-application-ticket.test.ts +233 -0
- package/packages/web-components/tests/uniplat-base-identity-ticket.test.ts +226 -0
- package/packages/web-components/tests/uniplat-base-organization-onboarding-transport.test.ts +227 -0
- package/packages/web-components/tests/user-login-transport.test.ts +443 -0
- package/packages/web-components/tests/user-login.test.ts +626 -0
- package/packages/web-components/tests/user-menu.test.ts +271 -0
- package/packages/web-components/tsconfig.json +8 -0
- package/packages/web-components/tsup.config.ts +12 -0
- package/packages/web-components/tsup.runtime.config.ts +37 -0
- package/pnpm-workspace.yaml +6 -0
- package/tsconfig.base.json +21 -0
- package/vitest.config.ts +20 -0
- package//344/272/244/346/216/245/346/226/207/346/241/243.md +254 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
export interface ApplicationEntry {
|
|
2
|
+
id: string
|
|
3
|
+
name: string
|
|
4
|
+
url: string
|
|
5
|
+
iconUrl?: string
|
|
6
|
+
description?: string
|
|
7
|
+
/** @deprecated Applications always open in a new window. */
|
|
8
|
+
openMode?: 'self' | 'blank'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface OrganizationEntry {
|
|
12
|
+
id: string
|
|
13
|
+
name: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface OrganizationListResult {
|
|
17
|
+
organizations: readonly OrganizationEntry[]
|
|
18
|
+
currentOrganizationId: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ApplicationLaunchResult {
|
|
22
|
+
url: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ApplicationSwitcherTransport {
|
|
26
|
+
listApplications(accessToken: string): Promise<readonly ApplicationEntry[]>
|
|
27
|
+
listOrganizations?(accessToken: string): Promise<OrganizationListResult>
|
|
28
|
+
switchOrganization?(accessToken: string, organizationId: string): Promise<void>
|
|
29
|
+
switchApplication?(
|
|
30
|
+
accessToken: string,
|
|
31
|
+
applicationId: string,
|
|
32
|
+
): Promise<ApplicationLaunchResult | void>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ApplicationSwitcherConfig {
|
|
36
|
+
getAccessToken(): Promise<string>
|
|
37
|
+
transport?: ApplicationSwitcherTransport
|
|
38
|
+
/**
|
|
39
|
+
* `embedded` renders only the application panel so another component can own
|
|
40
|
+
* the launcher and floating-panel lifecycle.
|
|
41
|
+
*/
|
|
42
|
+
presentation?: 'standalone' | 'embedded'
|
|
43
|
+
/** Defaults to false for embedded presentation and true otherwise. */
|
|
44
|
+
showOrganizationSelector?: boolean
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface HttpApplicationSwitcherTransportOptions {
|
|
48
|
+
endpoint: string
|
|
49
|
+
organizationsEndpoint?: string
|
|
50
|
+
switchOrganizationEndpoint?: string
|
|
51
|
+
fetch?: typeof fetch
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const MOCK_APPLICATION_ENTRIES: readonly ApplicationEntry[] = [
|
|
55
|
+
{ id: 'ai-employee', name: '数字员工', url: '/apps/ai-employee' },
|
|
56
|
+
{ id: 'enterprise-admin', name: '企业管理', url: '/apps/enterprise-admin' },
|
|
57
|
+
{ id: 'platform-admin', name: '平台管理', url: '/apps/platform-admin' },
|
|
58
|
+
{ id: 'recruitment', name: '招聘工作台', url: '/apps/recruitment' },
|
|
59
|
+
{ id: 'data-space', name: '数据空间', url: '/apps/data-space' },
|
|
60
|
+
{ id: 'toolbox', name: '工具箱', url: '/apps/toolbox' },
|
|
61
|
+
{ id: 'service-center', name: '服务中心', url: '/apps/service-center' },
|
|
62
|
+
{ id: 'knowledge-base', name: '知识库', url: '/apps/knowledge-base' },
|
|
63
|
+
{ id: 'kanban', name: '任务看板', url: '/apps/kanban' },
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
export const MOCK_ORGANIZATIONS: readonly OrganizationEntry[] = [
|
|
67
|
+
{ id: 'uniplat-group', name: 'Uniplat 集团' },
|
|
68
|
+
{ id: 'shanghai-branch', name: '上海分公司' },
|
|
69
|
+
{ id: 'hangzhou-rd', name: '杭州研发中心' },
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
export function createMockApplicationSwitcherTransport(): ApplicationSwitcherTransport {
|
|
73
|
+
let currentOrganizationId = MOCK_ORGANIZATIONS[0]?.id ?? ''
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
async listApplications(accessToken) {
|
|
77
|
+
requireAccessToken(accessToken)
|
|
78
|
+
return MOCK_APPLICATION_ENTRIES.map((entry) => ({ ...entry }))
|
|
79
|
+
},
|
|
80
|
+
async listOrganizations(accessToken) {
|
|
81
|
+
requireAccessToken(accessToken)
|
|
82
|
+
return {
|
|
83
|
+
organizations: MOCK_ORGANIZATIONS.map((organization) => ({ ...organization })),
|
|
84
|
+
currentOrganizationId,
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
async switchOrganization(accessToken, organizationId) {
|
|
88
|
+
requireAccessToken(accessToken)
|
|
89
|
+
const normalizedId = organizationId.trim()
|
|
90
|
+
if (!MOCK_ORGANIZATIONS.some((organization) => organization.id === normalizedId)) {
|
|
91
|
+
throw new Error('Organization is unavailable')
|
|
92
|
+
}
|
|
93
|
+
currentOrganizationId = normalizedId
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function createHttpApplicationSwitcherTransport(
|
|
99
|
+
options: HttpApplicationSwitcherTransportOptions,
|
|
100
|
+
): ApplicationSwitcherTransport {
|
|
101
|
+
const endpoint = validateEndpoint(options.endpoint, 'Application entry')
|
|
102
|
+
const organizationsEndpoint = options.organizationsEndpoint
|
|
103
|
+
? validateEndpoint(options.organizationsEndpoint, 'Organization list')
|
|
104
|
+
: ''
|
|
105
|
+
const switchOrganizationEndpoint = options.switchOrganizationEndpoint
|
|
106
|
+
? validateEndpoint(options.switchOrganizationEndpoint, 'Organization switch')
|
|
107
|
+
: ''
|
|
108
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch
|
|
109
|
+
|
|
110
|
+
const transport: ApplicationSwitcherTransport = {
|
|
111
|
+
async listApplications(accessToken) {
|
|
112
|
+
const token = requireAccessToken(accessToken)
|
|
113
|
+
|
|
114
|
+
const response = await request(fetchImplementation, endpoint, token, 'GET')
|
|
115
|
+
if (!response.ok) throw new Error('Application entry request failed')
|
|
116
|
+
return parseApplicationEntries(await response.json())
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (organizationsEndpoint) {
|
|
121
|
+
transport.listOrganizations = async (accessToken) => {
|
|
122
|
+
const token = requireAccessToken(accessToken)
|
|
123
|
+
const response = await request(fetchImplementation, organizationsEndpoint, token, 'GET')
|
|
124
|
+
if (!response.ok) throw new Error('Organization list request failed')
|
|
125
|
+
return parseOrganizations(await response.json())
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (switchOrganizationEndpoint) {
|
|
130
|
+
transport.switchOrganization = async (accessToken, organizationId) => {
|
|
131
|
+
const token = requireAccessToken(accessToken)
|
|
132
|
+
const normalizedId = organizationId.trim()
|
|
133
|
+
if (!normalizedId) throw new Error('Organization is required')
|
|
134
|
+
if (!fetchImplementation) throw new Error('Fetch is unavailable')
|
|
135
|
+
|
|
136
|
+
const response = await fetchImplementation(switchOrganizationEndpoint, {
|
|
137
|
+
method: 'POST',
|
|
138
|
+
credentials: 'include',
|
|
139
|
+
headers: {
|
|
140
|
+
accept: 'application/json',
|
|
141
|
+
authorization: `Bearer ${token}`,
|
|
142
|
+
'content-type': 'application/json',
|
|
143
|
+
},
|
|
144
|
+
body: JSON.stringify({ organizationId: normalizedId }),
|
|
145
|
+
})
|
|
146
|
+
if (!response.ok) throw new Error('Organization switch request failed')
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return transport
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function request(
|
|
154
|
+
fetchImplementation: typeof fetch | undefined,
|
|
155
|
+
endpoint: string,
|
|
156
|
+
accessToken: string,
|
|
157
|
+
method: 'GET',
|
|
158
|
+
): Promise<Response> {
|
|
159
|
+
if (!fetchImplementation) throw new Error('Fetch is unavailable')
|
|
160
|
+
return fetchImplementation(endpoint, {
|
|
161
|
+
method,
|
|
162
|
+
credentials: 'include',
|
|
163
|
+
headers: {
|
|
164
|
+
accept: 'application/json',
|
|
165
|
+
authorization: `Bearer ${accessToken}`,
|
|
166
|
+
},
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function parseApplicationEntries(payload: unknown): ApplicationEntry[] {
|
|
171
|
+
const response = asRecord(payload)
|
|
172
|
+
if (!Array.isArray(response.entries)) throw new Error('Invalid application entry response')
|
|
173
|
+
|
|
174
|
+
const entries: ApplicationEntry[] = []
|
|
175
|
+
for (const candidate of response.entries) {
|
|
176
|
+
const entry = asRecord(candidate)
|
|
177
|
+
const id = text(entry.id).trim()
|
|
178
|
+
const name = text(entry.name).trim()
|
|
179
|
+
const url = text(entry.url).trim()
|
|
180
|
+
if (!id || !name || !url) continue
|
|
181
|
+
|
|
182
|
+
const normalized: ApplicationEntry = { id, name, url }
|
|
183
|
+
const iconUrl = text(entry.iconUrl).trim()
|
|
184
|
+
const description = text(entry.description).trim()
|
|
185
|
+
if (iconUrl) normalized.iconUrl = iconUrl
|
|
186
|
+
if (description) normalized.description = description
|
|
187
|
+
if (entry.openMode === 'blank') normalized.openMode = 'blank'
|
|
188
|
+
entries.push(normalized)
|
|
189
|
+
}
|
|
190
|
+
return entries
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function parseOrganizations(payload: unknown): OrganizationListResult {
|
|
194
|
+
const response = asRecord(payload)
|
|
195
|
+
if (!Array.isArray(response.organizations)) throw new Error('Invalid organization response')
|
|
196
|
+
|
|
197
|
+
const organizations: OrganizationEntry[] = []
|
|
198
|
+
for (const candidate of response.organizations) {
|
|
199
|
+
const organization = asRecord(candidate)
|
|
200
|
+
const id = text(organization.id).trim()
|
|
201
|
+
const name = text(organization.name).trim()
|
|
202
|
+
if (id && name) organizations.push({ id, name })
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
organizations,
|
|
207
|
+
currentOrganizationId: text(response.currentOrganizationId).trim(),
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function requireAccessToken(accessToken: string): string {
|
|
212
|
+
const token = accessToken.trim()
|
|
213
|
+
if (!token) throw new Error('Access token is required')
|
|
214
|
+
return token
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function validateEndpoint(rawEndpoint: string, label: string): string {
|
|
218
|
+
const endpoint = typeof rawEndpoint === 'string' ? rawEndpoint.trim() : ''
|
|
219
|
+
if (!endpoint) throw new TypeError(`${label} endpoint is required`)
|
|
220
|
+
|
|
221
|
+
const baseUrl = typeof document === 'undefined' ? 'https://localhost/' : document.baseURI
|
|
222
|
+
let parsed: URL
|
|
223
|
+
try {
|
|
224
|
+
parsed = new URL(endpoint, baseUrl)
|
|
225
|
+
} catch {
|
|
226
|
+
throw new TypeError(`${label} endpoint is invalid`)
|
|
227
|
+
}
|
|
228
|
+
if (!['http:', 'https:'].includes(parsed.protocol) || parsed.username || parsed.password) {
|
|
229
|
+
throw new TypeError(`${label} endpoint must use HTTP(S) without user credentials`)
|
|
230
|
+
}
|
|
231
|
+
if (parsed.hash) throw new TypeError(`${label} endpoint must not contain a hash`)
|
|
232
|
+
return parsed.href
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function asRecord(value: unknown): Record<string, unknown> {
|
|
236
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
237
|
+
? (value as Record<string, unknown>)
|
|
238
|
+
: {}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function text(value: unknown): string {
|
|
242
|
+
return typeof value === 'string' ? value : ''
|
|
243
|
+
}
|