@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.
Files changed (90) hide show
  1. package/.prettierignore +4 -0
  2. package/.prettierrc.json +6 -0
  3. package/AGENTS.md +519 -0
  4. package/CLAUDE.md +512 -0
  5. package/README.md +734 -0
  6. 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
  7. 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
  8. 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
  9. 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
  10. package/eslint.config.js +30 -0
  11. package/examples/vanilla/index.html +33 -0
  12. package/examples/vanilla/main.js +247 -0
  13. package/examples/vanilla/package.json +15 -0
  14. package/examples/vanilla/public/runtime-config.js +10 -0
  15. package/examples/vanilla/style.css +99 -0
  16. package/package.json +31 -0
  17. package/packages/core/package.json +30 -0
  18. package/packages/core/src/client.ts +1132 -0
  19. package/packages/core/src/customer-support-client.ts +825 -0
  20. package/packages/core/src/customer-support-types.ts +123 -0
  21. package/packages/core/src/index.ts +79 -0
  22. package/packages/core/src/notification-center-client.ts +462 -0
  23. package/packages/core/src/notification-center-types.ts +154 -0
  24. package/packages/core/src/protocol.ts +237 -0
  25. package/packages/core/src/types.ts +170 -0
  26. package/packages/core/tests/client.test.ts +760 -0
  27. package/packages/core/tests/customer-support-client.test.ts +576 -0
  28. package/packages/core/tests/fake-websocket.ts +55 -0
  29. package/packages/core/tests/notification-center-client.test.ts +467 -0
  30. package/packages/core/tests/protocol.test.ts +124 -0
  31. package/packages/core/tsconfig.json +7 -0
  32. package/packages/core/tsup.config.ts +11 -0
  33. package/packages/web-components/package.json +35 -0
  34. package/packages/web-components/scripts/verify-runtime-build.mjs +111 -0
  35. package/packages/web-components/src/account-context.ts +1540 -0
  36. package/packages/web-components/src/application-switcher-transport.ts +243 -0
  37. package/packages/web-components/src/application-switcher.ts +1103 -0
  38. package/packages/web-components/src/chat.ts +1754 -0
  39. package/packages/web-components/src/customer-support-chat.ts +862 -0
  40. package/packages/web-components/src/customer-support-transport.ts +269 -0
  41. package/packages/web-components/src/index.ts +256 -0
  42. package/packages/web-components/src/notification-center.ts +1066 -0
  43. package/packages/web-components/src/notification-popover.ts +615 -0
  44. package/packages/web-components/src/organization-multi-employee-selector.ts +1564 -0
  45. package/packages/web-components/src/organization-onboarding-transport.ts +54 -0
  46. package/packages/web-components/src/organization-onboarding.ts +1216 -0
  47. package/packages/web-components/src/organization-selector.ts +534 -0
  48. package/packages/web-components/src/organization-single-employee-selector.ts +114 -0
  49. package/packages/web-components/src/prototype-icons.ts +242 -0
  50. package/packages/web-components/src/safe-image-url.ts +29 -0
  51. package/packages/web-components/src/safe-markdown.ts +192 -0
  52. package/packages/web-components/src/uniplat-base-account-context-transport.ts +625 -0
  53. package/packages/web-components/src/uniplat-base-application-switcher-transport.ts +418 -0
  54. package/packages/web-components/src/uniplat-base-application-ticket.ts +254 -0
  55. package/packages/web-components/src/uniplat-base-identity-ticket.ts +202 -0
  56. package/packages/web-components/src/uniplat-base-notification-center-transport.ts +378 -0
  57. package/packages/web-components/src/uniplat-base-organization-multi-employee-selector-transport.ts +394 -0
  58. package/packages/web-components/src/uniplat-base-organization-onboarding-transport.ts +183 -0
  59. package/packages/web-components/src/uniplat-base-organization-single-employee-selector-transport.ts +14 -0
  60. package/packages/web-components/src/user-login-transport.ts +563 -0
  61. package/packages/web-components/src/user-login.ts +2027 -0
  62. package/packages/web-components/src/user-menu.ts +880 -0
  63. package/packages/web-components/tests/account-context.test.ts +798 -0
  64. package/packages/web-components/tests/application-switcher.test.ts +562 -0
  65. package/packages/web-components/tests/chat.test.ts +1130 -0
  66. package/packages/web-components/tests/customer-support-chat.test.ts +428 -0
  67. package/packages/web-components/tests/customer-support-transport.test.ts +79 -0
  68. package/packages/web-components/tests/notification-center-transport.test.ts +271 -0
  69. package/packages/web-components/tests/notification-center.test.ts +429 -0
  70. package/packages/web-components/tests/notification-popover.test.ts +283 -0
  71. package/packages/web-components/tests/organization-multi-employee-selector-transport.test.ts +332 -0
  72. package/packages/web-components/tests/organization-multi-employee-selector.test.ts +225 -0
  73. package/packages/web-components/tests/organization-onboarding.test.ts +260 -0
  74. package/packages/web-components/tests/organization-selector.test.ts +127 -0
  75. package/packages/web-components/tests/organization-single-employee-selector.test.ts +145 -0
  76. package/packages/web-components/tests/uniplat-base-account-context-transport.test.ts +496 -0
  77. package/packages/web-components/tests/uniplat-base-application-switcher-transport.test.ts +598 -0
  78. package/packages/web-components/tests/uniplat-base-application-ticket.test.ts +233 -0
  79. package/packages/web-components/tests/uniplat-base-identity-ticket.test.ts +226 -0
  80. package/packages/web-components/tests/uniplat-base-organization-onboarding-transport.test.ts +227 -0
  81. package/packages/web-components/tests/user-login-transport.test.ts +443 -0
  82. package/packages/web-components/tests/user-login.test.ts +626 -0
  83. package/packages/web-components/tests/user-menu.test.ts +271 -0
  84. package/packages/web-components/tsconfig.json +8 -0
  85. package/packages/web-components/tsup.config.ts +12 -0
  86. package/packages/web-components/tsup.runtime.config.ts +37 -0
  87. package/pnpm-workspace.yaml +6 -0
  88. package/tsconfig.base.json +21 -0
  89. package/vitest.config.ts +20 -0
  90. 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
+ }