1/** 2 * authManager.js v0.2 3 * 4 * SNS ë¡ê·¸ì¸ ë° ì¸ì¦ ê´ë ¨ ê³µíµ ê¸°ë¥ ì ê³µ 5 * v2/memberManager.jsìì ë§ì´ê·¸ë ì´ì ë 기ë¥ì í¬í¨ 6 * 7 * ì¬ì©: 8 * const authManager = new AuthManager({ 9 * authServerHost: 'https://auth.campingtalk.me', 10 * accessTokenKey: 'ACCESS_TOKEN_KEY', 11 * refreshTokenKey: 'REFRESH_TOKEN_KEY' 12 * }); 13 */ 14 15class AuthManager { 16 constructor(options = {}) { 17 this.authServerHost = options.authServerHost || window.AUTH_SERVER_HOST || 'https://auth.campingtalk.me'; 18 this.accessTokenKey = options.accessTokenKey || 'ACCESS_TOKEN_KEY'; 19 this.refreshTokenKey = options.refreshTokenKey || 'REFRESH_TOKEN_KEY'; 20 21 // í í° 22 this.accessToken = localStorage.getItem(this.accessTokenKey) || ''; 23 this.refreshToken = localStorage.getItem(this.refreshTokenKey) || ''; 24 25 // ì¸ì¦ 컨í ì¤í¸ (cuid, clientType, serviceName, channelName) 26 this.cuid = options.cuid || ''; 27 this.clientType = this._resolveClientType(options.clientType); 28 this.serviceName = options.serviceName || 'booking'; 29 this.channelName = this._normalizeChannelName(options.channelName); 30 this.channelId = options.channelId || ''; 31 this.channelKey = options.channelKey || ''; 32 this.member = {}; 33 this.memberName = ''; 34 this.isAvailableMemberDetail = false; 35 } 36 37 /** 38 * í í° ì í¨ìê° íì¸ (ë¶ ë¨ì) 39 */ 40 validTime(accessToken) { 41 if (!accessToken) return 0; 42 43 try { 44 const payload = accessToken.split('.')[1]; 45 const decodedPayload = atob(payload); 46 const payloadObject = JSON.parse(decodedPayload); 47 48 const exp = new Date(payloadObject.exp * 1000); 49 const now = new Date(); 50 const diff = (exp.getTime() - now.getTime()) / (1000 * 60); 51 52 return diff; 53 } catch (e) { 54 return 0; 55 } 56 } 57 58 restoreTokens() { 59 const hasLocalContext = ['_cuid', '_clientType', '_serviceName', '_channelName'] 60 .some((key) => !!localStorage.getItem(key)); 61 if (hasLocalContext) this.restoreContextFromLocalStorage(); 62 else this.restoreContextFromCookie(); 63 64 const localAccessToken = localStorage.getItem(this.accessTokenKey); 65 const localRefreshToken = localStorage.getItem(this.refreshTokenKey); 66 if (localAccessToken || localRefreshToken) { 67 this.accessToken = localAccessToken || ''; 68 this.refreshToken = localRefreshToken || ''; 69 return; 70 } 71 this.accessToken = this._getCookie(this.accessTokenKey); 72 this.refreshToken = this._getCookie(this.refreshTokenKey); 73 } 74 75 reissue(callback) { 76 _get(`${this.authServerHost}/token/reissueAccessToken`, { 77 cuid: this.cuid, 78 refreshToken: this.refreshToken, 79 }, (response) => { 80 if (response.status === RestResponseStatus.OK) { 81 this.saveTokens(response.accessToken, response.refreshToken); 82 if (typeof callback === 'function') callback(response); 83 } else { 84 // 리íë ì í í°ë§ì ë§ë£/무í¨í ê²½ì° ìì¡´ í í°ì ì 리íë¤. 85 this.initAuth(); 86 } 87 }); 88 } 89 90 scheduleTokenRefresh() { 91 const remainingTime = this.validTime(this.accessToken); 92 if (remainingTime <= 0) return; 93 94 const timeout = 60000 * (remainingTime - (60 * 24 * 7)); 95 setTimeout(() => { 96 this.reissue(() => { 97 if (typeof StoreTokenToApp !== 'undefined') { 98 try { 99 StoreTokenToApp.postMessage('e'); 100 } catch (e) {} 101 } 102 this.scheduleTokenRefresh(); 103 }); 104 }, Math.max(0, timeout)); 105 } 106 107 /** 108 * íì¬ accessTokenì´ ì í¨íê° 109 */ 110 isValid() { 111 return this.validTime(this.accessToken) > 0; 112 } 113 114 get isMember() { 115 return this.isValid(); 116 } 117 118 async getMemberName() { 119 if (!this.isMember) return; 120 return new Promise((resolve) => { 121 _get('/rest/member/info', {}, async (response) => { 122 const member = await response.data.memberInfoRes; 123 this.member = member; 124 this.memberName = member.memberInfo.memberName; 125 this.isAvailableMemberDetail = member.isAvailableMemberDetail; 126 resolve(member); 127 }); 128 }); 129 } 130 131 isChannelMember() { 132 return ['benepia', 'etbs'].includes(this.channelName); 133 } 134 135 checkChannelMemberIsAuthorized(options = {}, callback) { 136 const channelId = options.channelId || this.channelId;
137 const channelKey = options.channelKey || this.channelKey; 138 if (!channelId || !channelKey) return; 139 _get(`/rest/domain/mall/channel/${channelId}/channelKey/${channelKey}`, {}, (res) => { 140 const isAuthorized = !!res.data.isAuthorized; 141 if (typeof callback === 'function') callback(isAuthorized, res); 142 }); 143 } 144 145 146 goChannelMemberAuthorizationPage(options = {}) { 147 const channelId = options.channelId || this.channelId; 148 const channelKey = options.channelKey || this.channelKey; 149 const serviceName = options.serviceName || `mall_${this.channelName}_promotion`; 150 if (!channelId || !channelKey) return; 151 window.location.href = `${this.authServerHost}/identification/channel/${channelId}/channelKey/${channelKey}/service/${serviceName}/apply`; 152 } 153 154 /** 155 * ì¸ì¦ ì´ê¸°í (ë¡ê·¸ìì ì í¸ì¶) 156 */ 157 initAuth() { 158 this.accessToken = ''; 159 this.refreshToken = ''; 160 localStorage.removeItem(this.accessTokenKey); 161 localStorage.removeItem(this.refreshTokenKey); 162 localStorage.removeItem('_cpt_reservation_token'); 163 //localStorage.removeItem('_cuid'); 164 //localStorage.removeItem('_clientType'); 165 //localStorage.removeItem('_serviceName'); 166 //localStorage.removeItem('_channelName'); 167 //localStorage.removeItem('_isFirstLogin'); 168 169 // ì¿ í¤ ìì 170 this._deleteCookie(this.accessTokenKey); 171 this._deleteCookie(this.refreshTokenKey); 172 this._deleteCookie('user-token'); 173 //this._deleteCookie('cuid'); 174 //this._deleteCookie('clientType'); 175 //this._deleteCookie('serviceName'); 176 //this._deleteCookie('channelName'); 177 178 // ì± íê²½ì´ë©´ ClearTokenToApp í¸ì¶ 179 if (typeof ClearTokenToApp !== 'undefined') { 180 try { 181 ClearTokenToApp.postMessage(''); 182 } catch (e) { 183 // 무ì 184 } 185 } 186 } 187 188 /** 189 * ë¹íì ì¸ì¦ ì´ê¸°í (ìì½ìë£ í¹ì ìì½ìì¸ íì¸ ì í¸ì¶) 190 */ 191 initNonMemberAuth() { 192 localStorage.removeItem('_cpt_reservation_token'); 193 194 // ì¿ í¤ ìì 195 this._deleteCookie('user-token'); 196 } 197 198 /** 199 * í í° ì ì¥ (accessToken, refreshToken) 200 */ 201 saveTokens(accessToken, refreshToken) { 202 if (accessToken) { 203 this.accessToken = accessToken; 204 localStorage.setItem(this.accessTokenKey, accessToken); 205 this._setCookie(this.accessTokenKey, accessToken, '31104000'); 206 } 207 if (refreshToken) { 208 this.refreshToken = refreshToken; 209 localStorage.setItem(this.refreshTokenKey, refreshToken); 210 this._setCookie(this.refreshTokenKey, refreshToken, '31104000'); 211 } 212 } 213 214 /** 215 * ì¸ì¦ 컨í ì¤í¸ ì ì¥/ë³µì 216 */ 217 ensureAuthContext(cuid, clientType, serviceName, channelName) { 218 const storedCuid = localStorage.getItem('_cuid'); 219 const storedClientType = localStorage.getItem('_clientType'); 220 const storedServiceName = localStorage.getItem('_serviceName'); 221 const storedChannelName = localStorage.getItem('_channelName'); 222 this.cuid = this._isUsableValue(cuid) ? cuid 223 : this._isUsableValue(this.cuid) ? this.cuid 224 : this._isUsableValue(storedCuid) ? storedCuid 225 : this._createCuid(); 226 this.clientType = this._resolveClientType(clientType, storedClientType, this.clientType); 227 this.serviceName = this._isUsableValue(serviceName) ? serviceName 228 : this._isUsableValue(storedServiceName) ? storedServiceName 229 : this.serviceName || 'booking'; 230 this.channelName = this._normalizeChannelName( 231 this._isUsableValue(channelName) ? channelName 232 : this._isUsableValue(storedChannelName) ? storedChannelName 233 : this.channelName 234 ); 235 236 this.saveContextToLocalStorage(); 237 this.saveContextToCookie(); 238 } 239 240 /** 241 * localStorageìì ì¸ì¦ 컨í ì¤í¸ ë³µì 242 */ 243 restoreContextFromLocalStorage() { 244 this.cuid = localStorage.getItem('_cuid') || this.cuid || ''; 245 this.clientType = localStorage.getItem('_clientType') || this.clientType || ''; 246 this.serviceName = localStorage.getItem('_serviceName') || this.serviceName || ''; 247 this.channelName = this._normalizeChannelName(localStorage.getItem('_channelName') || this.channelName); 248 } 249 250 /** 251 * ì¿ í¤ìì ì¸ì¦ 컨í ì¤í¸ ë³µì 252 */ 253 restoreContextFromCookie() { 254 this.cuid = this._getCookie('cuid') || this.cuid || ''; 255 this.clientType = this._getCookie('clientType') || this.clientType || ''; 256 this.serviceName = this._getCookie('serviceName') || this.serviceName || ''; 257 this.channelName = this._normalizeChannelName(this._getCookie('channelName') || this.channelName); 258 } 259 260 /** 261 * ì¸ì¦ 컨í ì¤í¸ë¥¼ localStorageì ì ì¥ 262 */ 263 saveContextToLocalStorage() { 264 if (this.cuid) localStorage.setItem('_cuid', this.cuid); 265 if (this.clientType) localStorage.setItem('_clientType', this.clientType); 266 if (this.serviceName) localStorage.setItem('_serviceName', this.serviceName); 267 if (this.channelName) localStorage.setItem('_channelName', this.channelName); 268 } 269 270 /** 271 * ì¸ì¦ 컨í ì¤í¸ë¥¼ ì¿ í¤ì ì ì¥ 272 */ 273 saveContextToCookie() { 274 if (this.cuid) this._setCookie('cuid', this.cuid, 1200); 275 if (this.clientType) this._setCookie('clientType', this.clientType, 1200); 276 if (this.serviceName) this._setCookie('serviceName', this.serviceName, 1200); 277 if (this.channelName) this._setCookie('channelName', this.channelName, 1200); 278 } 279 280 /** 281 * SNS ë¡ê·¸ì¸ URL êµ¬ì± 282 */ 283 buildSnsLoginUrl(type, options = {}) { 284 const params = new URLSearchParams(); 285 286 // 기본 íë¼ë¯¸í° 287 const cuid = options.cuid || this.cuid || ''; 288 const clientType = this._resolveClientType(options.clientType, this.clientType); 289 const serviceName = options.serviceName || this.serviceName || 'booking'; 290 const channelName = this._normalizeChannelName(options.channelName || this.channelName); 291 292 if (cuid) params.append('cuid', cuid); 293 params.append('clientType', clientType); 294 params.append('serviceName', serviceName); 295 if (channelName) params.append('channelName', channelName); 296 if (type) params.append('type', type); 297 // destinationUrië pathname + search를 모ë í¬í¨íë ìì í URL 298 if (options.destinationUri) params.append('destinationUri', options.destinationUri); 299 300 return `${this.authServerHost}/auth/snsLogin?${params.toString()}`; 301 } 302 303 /** 304 * SNS ë¡ê·¸ì¸ URLë§ ìì±íë¤. í¸ì¶ìê° ë§í¬ë¥¼ íìíê±°ë ë°íí ë ì¬ì©íë¤. 305 */ 306 getSnsLoginUrl(options = {}) { 307 this.ensureAuthContext( 308 options.cuid, 309 options.clientType, 310 options.serviceName, 311 options.channelName 312 ); 313 return this.buildSnsLoginUrl('', options); 314 } 315 316 /** 317 * ë¡ê·¸ì¸ 목ì ì§ë¥¼ ì±ëë³ localStorageì ì ì¥íë¤. 318 * ë¡ê·¸ì¸ ê³¼ì ìì channelSessionStorageê° ì ì¤ë ì ìì¼ë¯ë¡ 319 * URLê³¼ queryë§ì¼ë¡ ì¬ì§ì í ì ìëë¡ íë¤. 320 */ 321 saveLoginDestination(destinationUrl = null) { 322 if (typeof window === 'undefined') return; 323 324 let urlToSave; 325 try { 326 urlToSave = destinationUrl 327 // destinationUrië URLSearchParamsê° ì´ë¯¸ íìí query ì¸ì½ë©ì ì²ë¦¬íë¯ë¡
328 // URL ì ì²´ì decodeURIComponent를 ì ì©íì§ ìëë¤. query ê°ì %26, %3D ë±ì´ 329 // ì¤ì &, =ë¡ ë³íëë©´ íë¼ë¯¸í° ê²½ê³ê° ë°ëì´ destinationì´ ììë ì ìë¤. 330 ? new URL(destinationUrl, window.location.origin) 331 : new URL(window.location.href); 332 } catch (e) { 333 // ì못ë URLì 기존 fallback ëìì ì ì§íë¤. 334 urlToSave = destinationUrl 335 ? new URL(destinationUrl, window.location.origin) 336 : new URL(window.location.href); 337 } 338 339 const destinationUrlKey = typeof DESTINATION_URL_KEY !== 'undefined' 340 ? DESTINATION_URL_KEY : '_destination_url'; 341 const channelName = this._normalizeChannelName(this.channelName || localStorage.getItem('_channelName')); 342 const scope = channelName ? `@${channelName}/` : ''; 343 344 // pathname + search를 íµí©íì¬ ì ì¥ (query íë¼ë¯¸í° í¬í¨) 345 const fullUrl = urlToSave.pathname + urlToSave.search; 346 localStorage.setItem(`${scope}${destinationUrlKey}`, fullUrl); 347 } 348 349 /** 350 * SNS ë¡ê·¸ì¸ (location.href ë³ê²½) 351 */ 352 goSnsLogin(type = '', options = {}) { 353 try { 354 // type ìì´ í¸ì¶íë©´ SNS provider ì í íë©´ì¼ë¡ ì´ëíë¤. 355 if (typeof type === 'object') { 356 options = type; 357 type = ''; 358 } 359 360 this.ensureAuthContext( 361 options.cuid, 362 options.clientType, 363 options.serviceName, 364 options.channelName 365 ); 366 367 // destinationUri ì ë¬´ë¡ ì ì¥ì ê²°ì 368 if (options.destinationUri) { 369 this.saveLoginDestination(options.destinationUri); 370 } else { 371 // destinationUri ìì¼ë©´ currentUrl ì ì¥ (pathname + search í¬í¨) 372 const currentUrlKey = typeof CURRENT_URL_KEY !== 'undefined' 373 ? CURRENT_URL_KEY : '_current_url'; 374 const channelName = this._normalizeChannelName(options.channelName || this.channelName); 375 const scope = channelName ? `@${channelName}/` : ""; 376 localStorage.setItem(`${scope}${currentUrlKey}`, window.location.pathname + window.location.search); 377 } 378 379 // SNS ë¡ê·¸ì¸ URLë¡ ì´ë 380 const url = type ? this.buildSnsLoginUrl(type, options) : this.getSnsLoginUrl(options); 381 document.location = url; 382 } catch (e) { 383 console.error('goSnsLogin error:', e); 384 } 385 } 386 387 /** 388 * SNS ë¡ê·¸ì¸ redirect (ì ì¥ë context 기ë°) 389 * 60ê° íì¼ íµí©ì© í¬í¼ - ê¸°ë³¸ê° ì¬ì©, íìì ìµì ì¼ë¡ ì¤ë²ë¼ì´ë 390 */ 391 goSnsLoginRedirect(options = {}) { 392 return this.goSnsLogin(options); 393 } 394 395 /** 396 * 본ì¸ì¸ì¦ íì´ì§ë¡ ì´ë 397 */ 398 goIdentificationPage(serviceName, options = {}) { 399 try { 400 const params = new URLSearchParams(); 401 params.append('cuid', options.cuid || this.cuid); 402 params.append('clientType', options.clientType || this.clientType || 'web'); 403 params.append('serviceName', serviceName || 'booking'); 404 if (this.accessToken) { 405 params.append('accessToken', this.accessToken); 406 } 407 408 const url = `${this.authServerHost}/identification/member/apply?${params.toString()}`; 409 document.location = url; 410 } catch (e) { 411 console.error('goIdentificationPage error:', e); 412 } 413 } 414 415 /** 416 * ë¡ê·¸ìì 417 */ 418 logout(options = {}) { 419 const refreshToken = this.refreshToken; 420 421 if (typeof clientEnv !== 'undefined' && clientEnv.isApp) { 422 // ì± íê²½: POSTë¡ ë¡ê·¸ìì ì²ë¦¬ 423 this._postLogoutToApp(refreshToken, options); 424 } else { 425 // ì¹ íê²½: GETì¼ë¡ ë¡ê·¸ìì ì²ë¦¬ 426 this._getLogoutToWeb(refreshToken, options); 427 } 428 } 429 430 /** 431 * ì± íê²½ ë¡ê·¸ìì 432 */ 433 _postLogoutToApp(refreshToken, options) { 434 const url = `${window.location.origin}/rest/member/removePushIdentityAndLogout`; 435 const data = { refreshToken }; 436 437 // 기존 ì± í¸í ê³ì½(_post callback)ì ì°ì ì¬ì©íë¤. 438 if (typeof _post === 'function') { 439 _post('/rest/member/removePushIdentityAndLogout', data, () => { 440 this.initAuth(); 441 if (typeof CptLogout !== 'undefined') { 442 try { 443 CptLogout.postMessage(true); 444 } catch (e) {} 445 } 446 setTimeout(() => { 447 window.location.href = options.redirectUrl || '/main'; 448 }, 500); 449 }); 450 return; 451 } 452 453 if (typeof axios !== 'undefined') { 454 axios.post(url, data) 455 .then(() => { 456 this.initAuth(); 457 if (typeof CptLogout !== 'undefined') { 458 try { 459 CptLogout.postMessage(true); 460 } catch (e) {} 461 } 462 setTimeout(() => { 463 window.location.href = options.redirectUrl || '/main'; 464 }, 500); 465 }) 466 .catch(e => console.error('logout error:', e)); 467 } 468 } 469 470 /** 471 * ì¹ íê²½ ë¡ê·¸ìì 472 */ 473 _getLogoutToWeb(refreshToken, options) { 474 const url = `${this.authServerHost}/token/logout?refreshToken=${refreshToken}`; 475 476 if (typeof fetch !== 'undefined') { 477 fetch(url) 478 .then(() => { 479 this.initAuth(); 480 window.location.href = options.redirectUrl || '/main'; 481 }) 482 .catch(e => console.error('logout error:', e)); 483 } else { 484 // fallback 485 this.initAuth(); 486 window.location.href = options.redirectUrl || '/main'; 487 } 488 } 489 490 /** 491 * ì¿ í¤ ì¤ì í¬í¼ 492 */ 493 _setCookie(name, value, expires) {
494 let cookieString = `${name}=${encodeURIComponent(value)}`; 495 496 if (expires) { 497 if (typeof expires === 'string') { 498 cookieString += `; Max-Age=${expires}`; 499 } else { 500 cookieString += `; Max-Age=${expires}`; 501 } 502 } 503 504 cookieString += '; Path=/'; 505 document.cookie = cookieString; 506 } 507 508 /** 509 * ì¿ í¤ ê°ì ¸ì¤ê¸° í¬í¼ 510 */ 511 _getCookie(name) { 512 const nameEQ = `${name}=`; 513 const cookies = document.cookie.split(';'); 514 515 for (let i = 0; i < cookies.length; i++) { 516 let cookie = cookies[i].trim(); 517 if (cookie.startsWith(nameEQ)) { 518 return decodeURIComponent(cookie.substring(nameEQ.length)); 519 } 520 } 521 522 return ''; 523 } 524 525 /** 526 * ì¿ í¤ ìì í¬í¼ 527 */ 528 _deleteCookie(name) { 529 document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; 530 } 531 532 _isUsableValue(value) { 533 return value !== undefined && value !== null && value !== '' && value !== 'null' && value !== 'undefined'; 534 } 535 536 _resolveClientType(...fallbackValues) { 537 const appType = this._getAppType(); 538 if (this._isUsableValue(appType)) return appType; 539 540 for (const value of fallbackValues) { 541 if (this._isUsableValue(value)) return value; 542 } 543 return 'web'; 544 } 545 546 _getAppType() { 547 try { 548 const osName = clientEnv.additional.os.name.toLowerCase(); 549 if (clientEnv.isApp && osName === 'android') return 'android-app'; 550 if (clientEnv.isApp && osName === 'ios') return 'ios-app'; 551 return 'web'; 552 } catch (e) { 553 return ''; 554 } 555 } 556 557 _normalizeChannelName(channelName) { 558 return this._isUsableValue(channelName) ? channelName : 'campingtalk'; 559 } 560 561 _createCuid() { 562 const part = (dash) => { 563 const randomPart = (Math.random().toString(16) + '000000000').substring(2, 10); 564 return dash ? `-${randomPart.substring(0, 4)}-${randomPart.substring(4, 8)}` : randomPart; 565 }; 566 return part(false) + part(true) + part(true) + part(false); 567 } 568} 569 570// ê¸ë¡ë² ì¸ì¤í´ì¤ ìì± (ìµì ) 571if (typeof window !== 'undefined' && typeof window.authManager === 'undefined') { 572 // authManager를 ì ìì¼ë¡ ì ì¸íì§ ìì - ê° ëª¨ëìì ëª ìì ì¼ë¡ ìì±í´ì¼ í¨ 573}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.