ike-weblca-html 2.2.20 → 2.2.21

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ike-weblca-html",
3
- "version": "2.2.20",
3
+ "version": "2.2.21",
4
4
  "description": "weblca-html-header&footer",
5
5
  "main": "dist/index.js",
6
6
  "engines": {
@@ -9,7 +9,7 @@
9
9
  "scripts": {
10
10
  "start": "node scripts/dev-server.js",
11
11
  "build": "webpack",
12
- "test": "echo \"Error: no test specified\" && exit 1",
12
+ "test": "node tests/run-tests.js",
13
13
  "prepublishOnly": "npm run build",
14
14
  "release:patch": "npm version patch --no-git-tag-version && npm publish",
15
15
  "release:minor": "npm version minor --no-git-tag-version && npm publish",
@@ -2,6 +2,11 @@ import { css, html, LitElement } from 'lit-element'
2
2
  import { property } from 'lodash-es'
3
3
  import cookies from 'js-cookie'
4
4
  import { getResponseErrorMessage, REQUEST_ERROR_EVENT } from './js/requestError'
5
+ import {
6
+ applyAuthStateToHeader,
7
+ connectIframeHeaderAuth,
8
+ navigateWithParentFallback,
9
+ } from './js/HeaderAuthBridge'
5
10
 
6
11
  /**
7
12
  * IkeWebHeader - Web LCA 网站头部组件
@@ -543,6 +548,9 @@ export class IkeWebHeader extends LitElement {
543
548
 
544
549
  _requestErrorHandler = null
545
550
 
551
+ /** iframe 中与父页面同步登录展示状态的连接 */
552
+ _iframeAuthConnection = null
553
+
546
554
  /** 未读消息数量 */
547
555
  messageCount = 0
548
556
 
@@ -579,6 +587,12 @@ export class IkeWebHeader extends LitElement {
579
587
  }
580
588
  }
581
589
  window.addEventListener(REQUEST_ERROR_EVENT, this._requestErrorHandler)
590
+ this._iframeAuthConnection = connectIframeHeaderAuth({
591
+ windowObject: window,
592
+ onAuthState: (authState) => {
593
+ applyAuthStateToHeader(this, authState)
594
+ },
595
+ })
582
596
  this.fetchData()
583
597
  if (this.isLoggedIn) {
584
598
  this.fetchUserSummary()
@@ -595,6 +609,10 @@ export class IkeWebHeader extends LitElement {
595
609
  if (this._requestErrorHandler) {
596
610
  window.removeEventListener(REQUEST_ERROR_EVENT, this._requestErrorHandler)
597
611
  }
612
+ if (this._iframeAuthConnection) {
613
+ this._iframeAuthConnection.dispose()
614
+ this._iframeAuthConnection = null
615
+ }
598
616
  super.disconnectedCallback()
599
617
  }
600
618
 
@@ -1016,9 +1034,7 @@ export class IkeWebHeader extends LitElement {
1016
1034
 
1017
1035
  _goPersonalCenter(e) {
1018
1036
  if (e) e.stopPropagation()
1019
- window.location.href = this._replacePlatformDomain(
1020
- this._getPersonalCenterUrl()
1021
- )
1037
+ this._navigate(this._replacePlatformDomain(this._getPersonalCenterUrl()))
1022
1038
  }
1023
1039
 
1024
1040
  _logout(e) {
@@ -1028,9 +1044,24 @@ export class IkeWebHeader extends LitElement {
1028
1044
  cookies.remove(key)
1029
1045
  cookies.remove(key, { path: '/' })
1030
1046
  })
1047
+ if (
1048
+ this._iframeAuthConnection &&
1049
+ this._iframeAuthConnection.connected
1050
+ ) {
1051
+ this._iframeAuthConnection.logout()
1052
+ return
1053
+ }
1031
1054
  window.location.reload()
1032
1055
  }
1033
1056
 
1057
+ _navigate(url) {
1058
+ navigateWithParentFallback({
1059
+ windowObject: window,
1060
+ connection: this._iframeAuthConnection,
1061
+ url,
1062
+ })
1063
+ }
1064
+
1034
1065
  render() {
1035
1066
  return html` <div
1036
1067
  class="ike-web-header"
@@ -1469,8 +1500,7 @@ export class IkeWebHeader extends LitElement {
1469
1500
  to = loginUrl
1470
1501
  }
1471
1502
  to = this._replacePlatformDomain(to)
1472
- window.location.href = to
1473
- window.parent.postMessage({ type: 'navigate', url: to }, '*')
1503
+ this._navigate(to)
1474
1504
  }
1475
1505
 
1476
1506
  _clickLang(e) {
@@ -1496,14 +1526,12 @@ export class IkeWebHeader extends LitElement {
1496
1526
  _handleLoginClick() {
1497
1527
  if (this.isLoggedIn) {
1498
1528
  // 如果已登录,跳转到首页
1499
- window.location.href = this._replacePlatformDomain(
1500
- this._getPersonalCenterUrl()
1529
+ this._navigate(
1530
+ this._replacePlatformDomain(this._getPersonalCenterUrl())
1501
1531
  )
1502
1532
  } else {
1503
1533
  // 如果未登录,跳转到登录页面
1504
- window.location.href = this._replacePlatformDomain(
1505
- this._getHeaderLoginUrl()
1506
- )
1534
+ this._navigate(this._replacePlatformDomain(this._getHeaderLoginUrl()))
1507
1535
  }
1508
1536
  }
1509
1537
  }
package/src/index.js CHANGED
@@ -1,5 +1,45 @@
1
- import './IkeWebHeader'
2
- import './IkeWebFooter'
3
- import './js/UserAgreement'
4
-
5
- export { UserAgreementModal } from './js/UserAgreement'
1
+ import cookies from 'js-cookie'
2
+ import './IkeWebHeader'
3
+ import './IkeWebFooter'
4
+ import './js/UserAgreement'
5
+ import {
6
+ installParentAuthBridge,
7
+ readCookieAuthState,
8
+ } from './js/HeaderAuthBridge'
9
+
10
+ const bridgeInstalledKey = '__ikeWebHeaderAuthBridgeInstalled__'
11
+
12
+ function clearCurrentSiteSession() {
13
+ const allCookies = cookies.get() || {}
14
+ Object.keys(allCookies).forEach((key) => {
15
+ cookies.remove(key)
16
+ cookies.remove(key, { path: '/' })
17
+ })
18
+
19
+ try {
20
+ window.localStorage.clear()
21
+ window.sessionStorage.clear()
22
+ } catch (error) {
23
+ console.error('Error clearing browser storage:', error)
24
+ }
25
+ }
26
+
27
+ if (
28
+ typeof window !== 'undefined' &&
29
+ window.parent === window &&
30
+ !window[bridgeInstalledKey]
31
+ ) {
32
+ window[bridgeInstalledKey] = installParentAuthBridge({
33
+ windowObject: window,
34
+ getAuthState: () => readCookieAuthState(cookies),
35
+ onNavigate: (url) => {
36
+ window.location.href = url
37
+ },
38
+ onLogout: () => {
39
+ clearCurrentSiteSession()
40
+ window.location.reload()
41
+ },
42
+ })
43
+ }
44
+
45
+ export { UserAgreementModal } from './js/UserAgreement'
@@ -0,0 +1,374 @@
1
+ export const AUTH_REQUEST = 'ike:header-auth-request'
2
+ export const AUTH_RESPONSE = 'ike:header-auth-response'
3
+ export const NAVIGATE_REQUEST = 'ike:header-navigate-request'
4
+ export const LOGOUT_REQUEST = 'ike:header-logout-request'
5
+
6
+ const productionDomainSuffixes = [
7
+ 'weblca.net',
8
+ 'efootprint.net',
9
+ 'webcarbon.net',
10
+ 'webgpm.net',
11
+ ]
12
+
13
+ const developmentDomainSuffixes = [
14
+ 'manager.com',
15
+ 'efootprint.com',
16
+ 'webcarbon.com',
17
+ 'webgpm.com',
18
+ ]
19
+
20
+ const userSummaryFields = [
21
+ 'userName',
22
+ 'username',
23
+ 'realName',
24
+ 'name',
25
+ 'nickName',
26
+ 'relationId',
27
+ 'userId',
28
+ 'id',
29
+ 'userCode',
30
+ 'profileImg',
31
+ 'avatar',
32
+ 'headImg',
33
+ 'headUrl',
34
+ 'photo',
35
+ 'accountCount',
36
+ 'groupCount',
37
+ 'datasetCount',
38
+ 'materialCount',
39
+ ]
40
+
41
+ let requestSequence = 0
42
+
43
+ function matchesDomain(hostname, suffix) {
44
+ return hostname === suffix || hostname.endsWith(`.${suffix}`)
45
+ }
46
+
47
+ export function isTrustedPlatformOrigin(origin) {
48
+ try {
49
+ const url = new URL(origin)
50
+ const hostname = url.hostname.toLowerCase()
51
+ const isLocalHostname =
52
+ hostname === 'localhost' ||
53
+ hostname === '127.0.0.1' ||
54
+ hostname === '::1' ||
55
+ hostname.startsWith('192.168.10.')
56
+
57
+ if (isLocalHostname) {
58
+ return url.protocol === 'http:' || url.protocol === 'https:'
59
+ }
60
+
61
+ if (
62
+ productionDomainSuffixes.some((suffix) =>
63
+ matchesDomain(hostname, suffix)
64
+ )
65
+ ) {
66
+ return url.protocol === 'https:'
67
+ }
68
+
69
+ return (
70
+ (url.protocol === 'http:' || url.protocol === 'https:') &&
71
+ developmentDomainSuffixes.some((suffix) =>
72
+ matchesDomain(hostname, suffix)
73
+ )
74
+ )
75
+ } catch (error) {
76
+ return false
77
+ }
78
+ }
79
+
80
+ export function sanitizeAuthState(authState) {
81
+ const source = authState && typeof authState === 'object' ? authState : {}
82
+ const summarySource =
83
+ source.userSummary && typeof source.userSummary === 'object'
84
+ ? source.userSummary
85
+ : {}
86
+ const userSummary = {}
87
+
88
+ userSummaryFields.forEach((field) => {
89
+ if (summarySource[field] !== undefined) {
90
+ userSummary[field] = summarySource[field]
91
+ }
92
+ })
93
+
94
+ return {
95
+ loggedIn: Boolean(source.loggedIn),
96
+ userSummary,
97
+ messageCount: Math.max(0, Number(source.messageCount) || 0),
98
+ }
99
+ }
100
+
101
+ export function readCookieAuthState(cookieStore) {
102
+ if (!cookieStore || typeof cookieStore.get !== 'function') {
103
+ return sanitizeAuthState({ loggedIn: false })
104
+ }
105
+
106
+ const token = cookieStore.get('token')
107
+ if (!token) return sanitizeAuthState({ loggedIn: false })
108
+
109
+ let userInfo = {}
110
+ try {
111
+ const rawUserInfo = cookieStore.get('userInfo')
112
+ userInfo = rawUserInfo ? JSON.parse(rawUserInfo) : {}
113
+ } catch (error) {
114
+ userInfo = {}
115
+ }
116
+
117
+ return sanitizeAuthState({
118
+ loggedIn: true,
119
+ userSummary: userInfo,
120
+ messageCount: userInfo.messageCount,
121
+ })
122
+ }
123
+
124
+ export function applyAuthStateToHeader(header, authState) {
125
+ if (!header) return
126
+ const state = sanitizeAuthState(authState)
127
+ const data = state.userSummary
128
+
129
+ header.isLoggedIn = state.loggedIn
130
+ header.userSummary = data
131
+ header.userName =
132
+ data.userName ||
133
+ data.username ||
134
+ data.realName ||
135
+ data.name ||
136
+ data.nickName ||
137
+ ''
138
+ header.userId =
139
+ data.relationId || data.userId || data.id || data.userCode || ''
140
+ header.userAvatar =
141
+ data.profileImg ||
142
+ data.avatar ||
143
+ data.headImg ||
144
+ data.headUrl ||
145
+ data.photo ||
146
+ ''
147
+ header.userStats = {
148
+ account: data.accountCount || 0,
149
+ group: data.groupCount || 0,
150
+ database: data.datasetCount || 0,
151
+ databank: data.materialCount || 0,
152
+ }
153
+ header.messageCount = state.messageCount
154
+
155
+ if (typeof header.requestUpdate === 'function') header.requestUpdate()
156
+ }
157
+
158
+ export function isTrustedPlatformUrl(url, baseOrigin) {
159
+ try {
160
+ const targetUrl = new URL(url, baseOrigin)
161
+ return isTrustedPlatformOrigin(targetUrl.origin)
162
+ } catch (error) {
163
+ return false
164
+ }
165
+ }
166
+
167
+ function createRequestId() {
168
+ requestSequence += 1
169
+ return `ike-header-auth-${Date.now()}-${requestSequence}`
170
+ }
171
+
172
+ function getParentOrigin(windowObject) {
173
+ try {
174
+ const ancestorOrigins = windowObject.location.ancestorOrigins
175
+ if (ancestorOrigins && ancestorOrigins.length > 0) {
176
+ return new URL(ancestorOrigins[0]).origin
177
+ }
178
+ const referrer = windowObject.document && windowObject.document.referrer
179
+ if (!referrer) return ''
180
+ return new URL(referrer).origin
181
+ } catch (error) {
182
+ return ''
183
+ }
184
+ }
185
+
186
+ function isUsableMessageOrigin(origin) {
187
+ try {
188
+ const url = new URL(origin)
189
+ return url.protocol === 'http:' || url.protocol === 'https:'
190
+ } catch (error) {
191
+ return false
192
+ }
193
+ }
194
+
195
+ function getDirectIframe(windowObject, source) {
196
+ try {
197
+ const frames = windowObject.document.querySelectorAll('iframe')
198
+ return Array.from(frames).find((frame) => frame.contentWindow === source)
199
+ } catch (error) {
200
+ return undefined
201
+ }
202
+ }
203
+
204
+ function isDirectIframeMessage(windowObject, event) {
205
+ if (!isUsableMessageOrigin(event.origin)) return false
206
+ const frame = getDirectIframe(windowObject, event.source)
207
+ if (!frame || !frame.src) return false
208
+
209
+ try {
210
+ const frameOrigin = new URL(
211
+ frame.src,
212
+ windowObject.location.href || windowObject.location.origin
213
+ ).origin
214
+ return frameOrigin === event.origin
215
+ } catch (error) {
216
+ return false
217
+ }
218
+ }
219
+
220
+ export function installParentAuthBridge({
221
+ windowObject,
222
+ getAuthState,
223
+ onNavigate,
224
+ onLogout,
225
+ }) {
226
+ if (
227
+ !windowObject ||
228
+ windowObject.parent !== windowObject ||
229
+ typeof getAuthState !== 'function'
230
+ ) {
231
+ return () => {}
232
+ }
233
+
234
+ const handleMessage = async (event) => {
235
+ const data = event && event.data
236
+ if (
237
+ !event ||
238
+ !event.source ||
239
+ typeof event.source.postMessage !== 'function' ||
240
+ !isDirectIframeMessage(windowObject, event) ||
241
+ !data
242
+ ) {
243
+ return
244
+ }
245
+
246
+ if (data.type === NAVIGATE_REQUEST) {
247
+ if (
248
+ typeof onNavigate === 'function' &&
249
+ typeof data.url === 'string' &&
250
+ isTrustedPlatformUrl(data.url, windowObject.location.origin)
251
+ ) {
252
+ onNavigate(data.url)
253
+ }
254
+ return
255
+ }
256
+
257
+ if (data.type === LOGOUT_REQUEST) {
258
+ if (typeof onLogout === 'function') onLogout()
259
+ return
260
+ }
261
+
262
+ if (data.type !== AUTH_REQUEST || typeof data.requestId !== 'string') {
263
+ return
264
+ }
265
+
266
+ let authState
267
+ try {
268
+ authState = await getAuthState()
269
+ } catch (error) {
270
+ authState = { loggedIn: false }
271
+ }
272
+
273
+ event.source.postMessage(
274
+ {
275
+ type: AUTH_RESPONSE,
276
+ requestId: data.requestId,
277
+ authState: sanitizeAuthState(authState),
278
+ },
279
+ event.origin
280
+ )
281
+ }
282
+
283
+ windowObject.addEventListener('message', handleMessage)
284
+ return () => windowObject.removeEventListener('message', handleMessage)
285
+ }
286
+
287
+ export function connectIframeHeaderAuth({ windowObject, onAuthState }) {
288
+ if (
289
+ !windowObject ||
290
+ windowObject.parent === windowObject ||
291
+ typeof onAuthState !== 'function'
292
+ ) {
293
+ return { connected: false, dispose() {} }
294
+ }
295
+
296
+ const parentOrigin = getParentOrigin(windowObject)
297
+ if (!isUsableMessageOrigin(parentOrigin)) {
298
+ return { connected: false, dispose() {} }
299
+ }
300
+
301
+ const requestId = createRequestId()
302
+ let connected = false
303
+ const handleMessage = (event) => {
304
+ const data = event && event.data
305
+ if (
306
+ !event ||
307
+ event.source !== windowObject.parent ||
308
+ event.origin !== parentOrigin ||
309
+ !data ||
310
+ data.type !== AUTH_RESPONSE ||
311
+ data.requestId !== requestId
312
+ ) {
313
+ return
314
+ }
315
+
316
+ connected = true
317
+ onAuthState(sanitizeAuthState(data.authState))
318
+ }
319
+
320
+ windowObject.addEventListener('message', handleMessage)
321
+ windowObject.parent.postMessage({ type: AUTH_REQUEST, requestId }, parentOrigin)
322
+
323
+ return {
324
+ get connected() {
325
+ return connected
326
+ },
327
+ parentOrigin,
328
+ navigate(url) {
329
+ if (!connected) return false
330
+ if (!isTrustedPlatformUrl(url, parentOrigin)) return false
331
+ windowObject.parent.postMessage(
332
+ { type: NAVIGATE_REQUEST, url },
333
+ parentOrigin
334
+ )
335
+ return true
336
+ },
337
+ logout() {
338
+ if (!connected) return false
339
+ windowObject.parent.postMessage({ type: LOGOUT_REQUEST }, parentOrigin)
340
+ return true
341
+ },
342
+ dispose() {
343
+ connected = false
344
+ windowObject.removeEventListener('message', handleMessage)
345
+ },
346
+ }
347
+ }
348
+
349
+ export function navigateWithParentFallback({
350
+ windowObject,
351
+ connection,
352
+ url,
353
+ }) {
354
+ if (
355
+ connection &&
356
+ connection.connected &&
357
+ typeof connection.navigate === 'function' &&
358
+ connection.navigate(url)
359
+ ) {
360
+ return 'bridge'
361
+ }
362
+
363
+ windowObject.location.href = url
364
+ if (
365
+ windowObject.parent &&
366
+ windowObject.parent !== windowObject &&
367
+ typeof windowObject.parent.postMessage === 'function'
368
+ ) {
369
+ windowObject.parent.postMessage({ type: 'navigate', url }, '*')
370
+ return 'legacy'
371
+ }
372
+
373
+ return 'local'
374
+ }