国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

構建一個即時消息應用(七):Access頁面

開發
這是一系列關于構建“即時消息”應用的新帖子。現在我們已經完成了后端,讓我們轉到前端。 我將采用單頁應用程序方案。

[[345596]]

本文是該系列的第七篇。

現在我們已經完成了后端,讓我們轉到前端。 我將采用單頁應用程序方案。

首先,我們創建一個 static/index.html 文件,內容如下。

  1. <!DOCTYPE html> 
  2. <html lang="en"
  3. <head> 
  4.     <meta charset="utf-8"
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0"
  6.     <title>Messenger</title> 
  7.     <link rel="shortcut icon" href="data:,"
  8.     <link rel="stylesheet" href="/styles.css"
  9.     <script src="/main.js" type="module"></script> 
  10. </head> 
  11. <body></body> 
  12. </html> 

這個 HTML 文件必須為每個 URL 提供服務,并且使用 JavaScript 負責呈現正確的頁面。

因此,讓我們將注意力轉到 main.go 片刻,然后在 main() 函數中添加以下路由:

  1. router.Handle("GET""/...", http.FileServer(SPAFileSystem{http.Dir("static")})) 
  2.  
  3. type SPAFileSystem struct { 
  4.     fs http.FileSystem 
  5.  
  6. func (spa SPAFileSystem) Open(name string) (http.File, error) { 
  7.     f, err := spa.fs.Open(name
  8.     if err != nil { 
  9.         return spa.fs.Open("index.html"
  10.     } 
  11.     return f, nil 

我們使用一個自定義的文件系統,因此它不是為未知的 URL 返回 404 Not Found,而是轉到 index.html

路由器

在 index.html 中我們加載了兩個文件:styles.css 和 main.js。我把樣式留給你自由發揮。

讓我們移動到 main.js。 創建一個包含以下內容的 static/main.js 文件:

  1. import { guard } from './auth.js' 
  2. import Router from './router.js' 
  3.  
  4. let currentPage 
  5. const disconnect = new CustomEvent('disconnect'
  6. const router = new Router() 
  7.  
  8. router.handle('/', guard(view('home'), view('access'))) 
  9. router.handle('/callback'view('callback')) 
  10. router.handle(/^\/conversations\/([^\/]+)$/, guard(view('conversation'), view('access'))) 
  11. router.handle(/^\//, view('not-found')) 
  12.  
  13. router.install(async result => { 
  14.     document.body.innerHTML = '' 
  15.     if (currentPage instanceof Node) { 
  16.         currentPage.dispatchEvent(disconnect) 
  17.     } 
  18.     currentPage = await result 
  19.     if (currentPage instanceof Node) { 
  20.         document.body.appendChild(currentPage) 
  21.     } 
  22. }) 
  23.  
  24. function view(pageName) { 
  25.     return (...args) => import(`/pages/${pageName}-page.js`) 
  26.         .then(m => m.default(...args)) 

如果你是這個博客的關注者,你已經知道它是如何工作的了。 該路由器就是在 這里 顯示的那個。 只需從 @nicolasparada/router 下載并保存到 static/router.js 即可。

我們注冊了四條路由。 在根路由 / 處,我們展示 home 或 access 頁面,無論用戶是否通過身份驗證。 在 /callback 中,我們展示 callback 頁面。 在 /conversations/{conversationID} 上,我們展示對話或 access 頁面,無論用戶是否通過驗證,對于其他 URL,我們展示一個 not-found 頁面。

我們告訴路由器將結果渲染為文檔主體,并在離開之前向每個頁面調度一個 disconnect 事件。

我們將每個頁面放在不同的文件中,并使用新的動態 import() 函數導入它們。

身份驗證

guard() 是一個函數,給它兩個函數作為參數,如果用戶通過了身份驗證,則執行第一個函數,否則執行第二個。它來自 auth.js,所以我們創建一個包含以下內容的 static/auth.js 文件:

  1. export function isAuthenticated() { 
  2.     const token = localStorage.getItem('token'
  3.     const expiresAtItem = localStorage.getItem('expires_at'
  4.     if (token === null || expiresAtItem === null) { 
  5.         return false 
  6.     } 
  7.  
  8.     const expiresAt = new Date(expiresAtItem) 
  9.     if (isNaN(expiresAt.valueOf()) || expiresAt <= new Date()) { 
  10.         return false 
  11.     } 
  12.  
  13.     return true 
  14.  
  15. export function guard(fn1, fn2) { 
  16.     return (...args) => isAuthenticated() 
  17.         ? fn1(...args) 
  18.         : fn2(...args) 
  19.  
  20. export function getAuthUser() { 
  21.     if (!isAuthenticated()) { 
  22.         return null 
  23.     } 
  24.  
  25.     const authUser = localStorage.getItem('auth_user'
  26.     if (authUser === null) { 
  27.         return null 
  28.     } 
  29.  
  30.     try { 
  31.         return JSON.parse(authUser) 
  32.     } catch (_) { 
  33.         return null 
  34.     } 

isAuthenticated() 檢查 localStorage 中的 token 和 expires_at,以判斷用戶是否已通過身份驗證。getAuthUser() 從 localStorage 中獲取經過身份驗證的用戶。

當我們登錄時,我們會將所有的數據保存到 localStorage,這樣才有意義。

Access 頁面

 

access page screenshot

讓我們從 access 頁面開始。 創建一個包含以下內容的文件 static/pages/access-page.js

  1. const template = document.createElement('template'
  2. template.innerHTML = ` 
  3.     <h1>Messenger</h1> 
  4.     <a href="/api/oauth/github" onclick="event.stopPropagation()">Access with GitHub</a> 
  5.  
  6. export default function accessPage() { 
  7.     return template.content 

因為路由器會攔截所有鏈接點擊來進行導航,所以我們必須特別阻止此鏈接的事件傳播。

單擊該鏈接會將我們重定向到后端,然后重定向到 GitHub,再重定向到后端,然后再次重定向到前端; 到 callback 頁面。

Callback 頁面

創建包括以下內容的 static/pages/callback-page.js 文件:

  1. import http from '../http.js' 
  2. import { navigate } from '../router.js' 
  3.  
  4. export default async function callbackPage() { 
  5.     const url = new URL(location.toString()) 
  6.     const token = url.searchParams.get('token'
  7.     const expiresAt = url.searchParams.get('expires_at'
  8.  
  9.     try { 
  10.         if (token === null || expiresAt === null) { 
  11.             throw new Error('Invalid URL'
  12.         } 
  13.  
  14.         const authUser = await getAuthUser(token) 
  15.  
  16.         localStorage.setItem('auth_user', JSON.stringify(authUser)) 
  17.         localStorage.setItem('token', token) 
  18.         localStorage.setItem('expires_at', expiresAt) 
  19.     } catch (err) { 
  20.         alert(err.message) 
  21.     } finally { 
  22.         navigate('/'true
  23.     } 
  24.  
  25. function getAuthUser(token) { 
  26.     return http.get('/api/auth_user', { authorization: `Bearer ${token}` }) 

callback 頁面不呈現任何內容。這是一個異步函數,它使用 URL 查詢字符串中的 token 向 /api/auth_user 發出 GET 請求,并將所有數據保存到 localStorage。 然后重定向到 /

HTTP

這里是一個 HTTP 模塊。 創建一個包含以下內容的 static/http.js 文件:

  1. import { isAuthenticated } from './auth.js' 
  2.  
  3. async function handleResponse(res) { 
  4.     const body = await res.clone().json().catch(() => res.text()) 
  5.  
  6.     if (res.status === 401) { 
  7.         localStorage.removeItem('auth_user'
  8.         localStorage.removeItem('token'
  9.         localStorage.removeItem('expires_at'
  10.     } 
  11.  
  12.     if (!res.ok) { 
  13.         const message = typeof body === 'object' && body !== null && 'message' in body 
  14.             ? body.message 
  15.             : typeof body === 'string' && body !== '' 
  16.                 ? body 
  17.                 : res.statusText 
  18.         throw Object.assign(new Error(message), { 
  19.             url: res.url, 
  20.             statusCode: res.status, 
  21.             statusText: res.statusText, 
  22.             headers: res.headers, 
  23.             body, 
  24.         }) 
  25.     } 
  26.  
  27.     return body 
  28.  
  29. function getAuthHeader() { 
  30.     return isAuthenticated() 
  31.         ? { authorization: `Bearer ${localStorage.getItem('token')}` } 
  32.         : {} 
  33.  
  34. export default { 
  35.     get(url, headers) { 
  36.         return fetch(url, { 
  37.             headers: Object.assign(getAuthHeader(), headers), 
  38.         }).then(handleResponse) 
  39.     }, 
  40.  
  41.     post(url, body, headers) { 
  42.         const init = { 
  43.             method: 'POST'
  44.             headers: getAuthHeader(), 
  45.         } 
  46.         if (typeof body === 'object' && body !== null) { 
  47.             init.body = JSON.stringify(body) 
  48.             init.headers['content-type'] = 'application/json; charset=utf-8' 
  49.         } 
  50.         Object.assign(init.headers, headers) 
  51.         return fetch(url, init).then(handleResponse) 
  52.     }, 
  53.  
  54.     subscribe(url, callback) { 
  55.         const urlWithToken = new URL(url, location.origin) 
  56.         if (isAuthenticated()) { 
  57.             urlWithToken.searchParams.set('token', localStorage.getItem('token')) 
  58.         } 
  59.         const eventSource = new EventSource(urlWithToken.toString()) 
  60.         eventSource.onmessage = ev => { 
  61.             let data 
  62.             try { 
  63.                 data = JSON.parse(ev.data) 
  64.             } catch (err) { 
  65.                 console.error('could not parse message data as JSON:', err) 
  66.                 return 
  67.             } 
  68.             callback(data) 
  69.         } 
  70.         const unsubscribe = () => { 
  71.             eventSource.close() 
  72.         } 
  73.         return unsubscribe 
  74.     }, 

這個模塊是 fetch 和 EventSource API 的包裝器。最重要的部分是它將 JSON web 令牌添加到請求中。

Home 頁面

 

home page screenshot

因此,當用戶登錄時,將顯示 home 頁。 創建一個具有以下內容的 static/pages/home-page.js 文件:

  1. import { getAuthUser } from '../auth.js' 
  2. import { avatar } from '../shared.js' 
  3.  
  4. export default function homePage() { 
  5.     const authUser = getAuthUser() 
  6.     const template = document.createElement('template'
  7.     template.innerHTML = ` 
  8.         <div> 
  9.             <div> 
  10.                 ${avatar(authUser)} 
  11.                 <span>${authUser.username}</span> 
  12.             </div> 
  13.             <button id="logout-button">Logout</button> 
  14.         </div> 
  15.         <!-- conversation form here --> 
  16.         <!-- conversation list here --> 
  17.     ` 
  18.     const page = template.content 
  19.     page.getElementById('logout-button').onclick = onLogoutClick 
  20.     return page 
  21.  
  22. function onLogoutClick() { 
  23.     localStorage.clear() 
  24.     location.reload() 

對于這篇文章,這是我們在 home 頁上呈現的唯一內容。我們顯示當前經過身份驗證的用戶和注銷按鈕。

當用戶單擊注銷時,我們清除 localStorage 中的所有內容并重新加載頁面。

Avatar

那個 avatar() 函數用于顯示用戶的頭像。 由于已在多個地方使用,因此我將它移到 shared.js 文件中。 創建具有以下內容的文件 static/shared.js

  1. export function avatar(user) { 
  2.     return user.avatarUrl === null 
  3.         ? `<figure class="avatar" data-initial="${user.username[0]}"></figure>` 
  4.         : `<img class="avatar" src="${user.avatarUrl}" alt="${user.username}'s avatar">` 

如果頭像網址為 null,我們將使用用戶的姓名首字母作為初始頭像。

你可以使用 attr() 函數顯示帶有少量 CSS 樣式的首字母。

  1. .avatar[data-initial]::after { 
  2.     content: attr(data-initial); 

僅開發使用的登錄

 

access page with login form screenshot

在上一篇文章中,我們為編寫了一個登錄代碼。讓我們在 access 頁面中為此添加一個表單。 進入 static/ages/access-page.js,稍微修改一下。

  1. import http from '../http.js' 
  2.  
  3. const template = document.createElement('template'
  4. template.innerHTML = ` 
  5.     <h1>Messenger</h1> 
  6.     <form id="login-form"
  7.         <input type="text" placeholder="Username" required> 
  8.         <button>Login</button> 
  9.     </form> 
  10.     <a href="/api/oauth/github" onclick="event.stopPropagation()">Access with GitHub</a> 
  11.  
  12. export default function accessPage() { 
  13.     const page = template.content.cloneNode(true
  14.     page.getElementById('login-form').onsubmit = onLoginSubmit 
  15.     return page 
  16.  
  17. async function onLoginSubmit(ev) { 
  18.     ev.preventDefault() 
  19.  
  20.     const form = ev.currentTarget 
  21.     const input = form.querySelector('input'
  22.     const submitButton = form.querySelector('button'
  23.  
  24.     input.disabled = true 
  25.     submitButton.disabled = true 
  26.  
  27.     try { 
  28.         const payload = await login(input.value) 
  29.         input.value = '' 
  30.  
  31.         localStorage.setItem('auth_user', JSON.stringify(payload.authUser)) 
  32.         localStorage.setItem('token', payload.token) 
  33.         localStorage.setItem('expires_at', payload.expiresAt) 
  34.  
  35.         location.reload() 
  36.     } catch (err) { 
  37.         alert(err.message) 
  38.         setTimeout(() => { 
  39.             input.focus() 
  40.         }, 0) 
  41.     } finally { 
  42.         input.disabled = false 
  43.         submitButton.disabled = false 
  44.     } 
  45.  
  46. function login(username) { 
  47.     return http.post('/api/login', { username }) 

我添加了一個登錄表單。當用戶提交表單時。它使用用戶名對 /api/login 進行 POST 請求。將所有數據保存到 localStorage 并重新加載頁面。

記住在前端完成后刪除此表單。


這就是這篇文章的全部內容。在下一篇文章中,我們將繼續使用主頁添加一個表單來開始對話,并顯示包含最新對話的列表。

 

責任編輯:龐桂玉 來源: Linux中國
相關推薦

2020-10-16 14:40:20

即時消息Home頁面編程語言

2020-10-19 16:20:38

即時消息Conversatio編程語言

2020-10-09 12:45:19

創建消息即時消息編程語言

2019-09-29 15:25:13

CockroachDBGoJavaScript

2020-10-09 15:00:56

實時消息編程語言

2020-03-31 12:21:20

JSON即時消息編程語言

2019-10-28 20:12:40

OAuthGuard中間件編程語言

2020-10-10 20:51:10

即時消息編程語言

2023-08-14 08:01:12

websocket8g用戶

2021-03-25 08:29:33

SpringBootWebSocket即時消息

2025-06-30 01:45:00

Netty輪詢HTTP 協議

2015-03-18 15:37:19

社交APP場景

2021-04-03 12:31:48

Python開發數據科學

2014-10-15 11:01:02

Web應用測試應用

2018-08-22 17:32:45

2022-02-10 07:03:32

流量應用架構數據交換

2021-07-14 17:39:46

ReactRails API前端組件

2023-09-21 08:00:00

ChatGPT編程工具

2021-12-03 00:02:01

通訊工具即時

2023-09-15 10:10:05

R 語言
點贊
收藏

51CTO技術棧公眾號

精品福利视频一区二区三区| 欧美韩日亚洲| 日韩亚洲精品视频| 国产又黄又大久久| www.超碰在线| 妺妺窝人体色www看人体| 亚洲欧美在线播放| 国产成人免费xxxxxxxx| yy6080久久伦理一区二区| wwwwww欧美| 精品国产一区二区三区久久狼黑人 | 国产天堂素人系列在线视频| 91社区国产高清| 欧美日韩欧美一区二区| 日韩专区一卡二卡| 国精产品一区二区三区有限公司 | ****av在线网毛片| 欧美一级视频在线播放| 欧美激情伊人电影| 精品国产鲁一鲁一区二区张丽| 欧美久色视频| av在线视屏| 人妻熟女一二三区夜夜爱| 日本精品免费观看| 欧美综合在线视频| 久久国产三级精品| 麻豆视频久久| 亚洲日本高清| 天天综合狠狠精品| 久久成人综合视频| 亚洲午夜日本在线观看| 在线视频亚洲| 亚洲精品555| 日本免费专区| 欧美专区一二三| 免费不卡欧美自拍视频| 亚洲成人免费影院| 日韩av在线发布| 亚洲日本va| 在线国产1区| 亚洲免费视频播放| 欧美一级黄色网| 欧美一卡二卡在线观看| 成人性视频免费网站| 精品免费在线| 超碰激情在线| 中文字幕福利片| 中文字幕日韩一区二区三区| 欧美一区二区| 欧美激情精品久久久久| 亚洲日本电影在线| 小嫩嫩精品导航| 999久久久国产999久久久| 美日韩黄色片| 婷婷精品国产一区二区三区日韩| 欧美精品生活片| 欧美专区日韩专区| 成人深夜在线观看| 日本久久精品| 亚洲涩涩在线| 精品国产一区二区三区四区阿崩| 亚洲aⅴ天堂av在线电影软件| 久久久亚洲精选| 欧美三级电影在线看| www久久久久| 亚洲经典在线看| 日韩城人网站| 成年人在线视频| 无码内射中文字幕岛国片| 国产尤物99| 久久久亚洲国产天美传媒修理工| 制服丝袜一区二区三区| 欧美经典一区二区| 日韩高清欧美激情| 国产一区三区在线播放| 卡通欧美亚洲| 国产精品99999| 色综合手机在线| 色吧亚洲视频| 国产精品自产拍高潮在线观看| 国产亚洲人成a一在线v站| 色哟哟亚洲精品| 国产精品婷婷午夜在线观看| 秋霞国产午夜精品免费视频| 黄色美女久久久| 中文在线免费视频| 91在线观看| 蜜桃视频免费网站| 国产中文字幕乱人伦在线观看| 国产精品一区视频网站| 97在线观看免费高清| 亚洲欧美变态国产另类| 欧洲一区二区三区在线| 中文幕一区二区三区久久蜜桃| 精彩视频一区二区三区| 激情欧美亚洲| 精品av一区二区| 视频欧美一区| 成人开心激情| 国产三级伦理在线| 国产毛片av在线| 97在线资源| 污污视频网站免费观看| 免费日韩在线观看| 日韩av电影免费观看| 成人午夜两性视频| 欧美激情在线观看| 最好看的2019的中文字幕视频| 精品区一区二区| 欧美日韩在线观看一区二区| 亚洲大型综合色站| 亚洲欧美自拍偷拍色图| 91性感美女视频| 国产**成人网毛片九色| 免费不卡在线观看| 国产深夜精品| 激情综合中文娱乐网| 久久精品国内一区二区三区水蜜桃| 秋霞蜜臀av久久电影网免费| 国内不卡的一区二区三区中文字幕| 在线手机中文字幕| 欧美四级在线| 天堂8中文在线| www视频在线免费观看| 精品一区二区三区视频| 亚洲欧美怡红院| 欧美亚洲成人免费| 瑟瑟视频在线| 久久精品一区| www.精品国产| 国产成人精品123区免费视频| 美腿丝袜在线亚洲一区| 久热国产精品视频| 秋霞av在线| 国产午夜亚洲精品羞羞网站| 国产精品免费视频xxxx| 污视频免费在线观看| 中文字幕一区二区三区在线观看 | 精品欧美一区二区三区| 日本男女交配视频| 亚洲在线视频| 在线视频欧美日韩精品| 深夜黄色小视频| 亚洲美女久久| 日韩精品在线看片z| 国产精品333| 精品一区二区三区视频在线观看| 中文字幕亚洲一区二区三区| 欧美一级裸体视频| 久久精品亚洲| 欧美视频小说| 成人午夜精品在线| 国产精品综合不卡av| 国产精品一区二区三区av| 91精品啪在线观看国产60岁| 午夜视频在线免费| 亚洲在线观看| 国产精品乱子乱xxxx| 99热免费精品在线观看| av日韩免费电影| 一区二区三区在线| 97超级碰碰| 国产精品国产三级国产在线观看 | 激情图区综合网| 99re在线视频观看| 天天影视综合| 国产在线视频2019最新视频| 美女呻吟一区| 97热精品视频官网| 国产精品国产亚洲精品| 亚洲国产精品久久久久| 99精品人妻少妇一区二区| 国产高清在线观看免费不卡| 国产精品www色诱视频| 91国内外精品自在线播放| 在线精品视频免费播放| 国产1区2区在线| 国产成人在线视频网站| 人人妻人人澡人人爽精品欧美一区| 久久视频一区二区| 国产高清免费在线| 在线观看不卡| 亚洲精品中字| 国产成人自拍网| 欧美网站免费观看| 久久最新视频| 先锋影音一区二区三区| 国产盗摄精品一区二区三区在线| 日本中文不卡| 日韩欧美不卡在线观看视频| 亚洲人午夜色婷婷| 97在线精品国自产拍中文| 999在线观看免费大全电视剧| 在线观看欧美激情| 成人天堂av| 麻豆视频在线| 欧美一级大片在线视频| 欧美xxxx免费虐| 国产精品午夜春色av| 999热视频在线观看|