diff --git a/sources/tech/20180716 Building a Messenger App- Access Page.md b/sources/tech/20180716 Building a Messenger App- Access Page.md
new file mode 100644
index 0000000000..21671b92f6
--- /dev/null
+++ b/sources/tech/20180716 Building a Messenger App- Access Page.md
@@ -0,0 +1,459 @@
+[#]: collector: (lujun9972)
+[#]: translator: ( )
+[#]: reviewer: ( )
+[#]: publisher: ( )
+[#]: url: ( )
+[#]: subject: (Building a Messenger App: Access Page)
+[#]: via: (https://nicolasparada.netlify.com/posts/go-messenger-access-page/)
+[#]: author: (Nicolás Parada https://nicolasparada.netlify.com/)
+
+Building a Messenger App: Access Page
+======
+
+This post is the 7th on a series:
+
+ * [Part 1: Schema][1]
+ * [Part 2: OAuth][2]
+ * [Part 3: Conversations][3]
+ * [Part 4: Messages][4]
+ * [Part 5: Realtime Messages][5]
+ * [Part 6: Development Login][6]
+
+
+
+Now that we’re done with the backend, lets move to the frontend. I will go with a single-page application.
+
+Lets start by creating a file `static/index.html` with the following content.
+
+```
+
+
+
+
+
+ Messenger
+
+
+
+
+
+
+```
+
+This HTML file must be server for every URL and JavaScript will take care of rendering the correct page.
+
+So lets go the the `main.go` for a moment and in the `main()` function add the following route:
+
+```
+router.Handle("GET", "/...", http.FileServer(SPAFileSystem{http.Dir("static")}))
+
+type SPAFileSystem struct {
+ fs http.FileSystem
+}
+
+func (spa SPAFileSystem) Open(name string) (http.File, error) {
+ f, err := spa.fs.Open(name)
+ if err != nil {
+ return spa.fs.Open("index.html")
+ }
+ return f, nil
+}
+```
+
+We use a custom file system so instead of returning `404 Not Found` for unknown URLs, it serves the `index.html`.
+
+### Router
+
+In the `index.html` we loaded two files: `styles.css` and `main.js`. I leave styling to your taste.
+
+Lets move to `main.js`. Create a `static/main.js` file with the following content:
+
+```
+import { guard } from './auth.js'
+import Router from './router.js'
+
+let currentPage
+const disconnect = new CustomEvent('disconnect')
+const router = new Router()
+
+router.handle('/', guard(view('home'), view('access')))
+router.handle('/callback', view('callback'))
+router.handle(/^\/conversations\/([^\/]+)$/, guard(view('conversation'), view('access')))
+router.handle(/^\//, view('not-found'))
+
+router.install(async result => {
+ document.body.innerHTML = ''
+ if (currentPage instanceof Node) {
+ currentPage.dispatchEvent(disconnect)
+ }
+ currentPage = await result
+ if (currentPage instanceof Node) {
+ document.body.appendChild(currentPage)
+ }
+})
+
+function view(pageName) {
+ return (...args) => import(`/pages/${pageName}-page.js`)
+ .then(m => m.default(...args))
+}
+```
+
+If you are follower of this blog, you already know how this works. That router is the one showed [here][7]. Just download it from [@nicolasparada/router][8] and save it to `static/router.js`.
+
+We registered four routes. At the root `/` we show the home or access page whether the user is authenticated. At `/callback` we show the callback page. On `/conversations/{conversationID}` we show the conversation or access page whether the user is authenticated and for every other URL, we show a not found page.
+
+We tell the router to render the result to the document body and dispatch a `disconnect` event to each page before leaving.
+
+We have each page in a different file and we import them with the new dynamic `import()`.
+
+### Auth
+
+`guard()` is a function that given two functions, executes the first one if the user is authenticated, or the sencond one if not. It comes from `auth.js` so lets create a `static/auth.js` file with the following content:
+
+```
+export function isAuthenticated() {
+ const token = localStorage.getItem('token')
+ const expiresAtItem = localStorage.getItem('expires_at')
+ if (token === null || expiresAtItem === null) {
+ return false
+ }
+
+ const expiresAt = new Date(expiresAtItem)
+ if (isNaN(expiresAt.valueOf()) || expiresAt <= new Date()) {
+ return false
+ }
+
+ return true
+}
+
+export function guard(fn1, fn2) {
+ return (...args) => isAuthenticated()
+ ? fn1(...args)
+ : fn2(...args)
+}
+
+export function getAuthUser() {
+ if (!isAuthenticated()) {
+ return null
+ }
+
+ const authUser = localStorage.getItem('auth_user')
+ if (authUser === null) {
+ return null
+ }
+
+ try {
+ return JSON.parse(authUser)
+ } catch (_) {
+ return null
+ }
+}
+```
+
+`isAuthenticated()` checks for `token` and `expires_at` from localStorage to tell if the user is authenticated. `getAuthUser()` gets the authenticated user from localStorage.
+
+When we login, we’ll save all the data to localStorage so it will make sense.
+
+### Access Page
+
+![access page screenshot][9]
+
+Lets start with the access page. Create a file `static/pages/access-page.js` with the following content:
+
+```
+const template = document.createElement('template')
+template.innerHTML = `
+
Messenger
+ Access with GitHub
+`
+
+export default function accessPage() {
+ return template.content
+}
+```
+
+Because the router intercepts all the link clicks to do its navigation, we must prevent the event propagation for this link in particular.
+
+Clicking on that link will redirect us to the backend, then to GitHub, then to the backend and then to the frontend again; to the callback page.
+
+### Callback Page
+
+Create the file `static/pages/callback-page.js` with the following content:
+
+```
+import http from '../http.js'
+import { navigate } from '../router.js'
+
+export default async function callbackPage() {
+ const url = new URL(location.toString())
+ const token = url.searchParams.get('token')
+ const expiresAt = url.searchParams.get('expires_at')
+
+ try {
+ if (token === null || expiresAt === null) {
+ throw new Error('Invalid URL')
+ }
+
+ const authUser = await getAuthUser(token)
+
+ localStorage.setItem('auth_user', JSON.stringify(authUser))
+ localStorage.setItem('token', token)
+ localStorage.setItem('expires_at', expiresAt)
+ } catch (err) {
+ alert(err.message)
+ } finally {
+ navigate('/', true)
+ }
+}
+
+function getAuthUser(token) {
+ return http.get('/api/auth_user', { authorization: `Bearer ${token}` })
+}
+```
+
+The callback page doesn’t render anything. It’s an async function that does a GET request to `/api/auth_user` using the token from the URL query string and saves all the data to localStorage. Then it redirects to `/`.
+
+### HTTP
+
+There is an HTTP module. Create a `static/http.js` file with the following content:
+
+```
+import { isAuthenticated } from './auth.js'
+
+async function handleResponse(res) {
+ const body = await res.clone().json().catch(() => res.text())
+
+ if (res.status === 401) {
+ localStorage.removeItem('auth_user')
+ localStorage.removeItem('token')
+ localStorage.removeItem('expires_at')
+ }
+
+ if (!res.ok) {
+ const message = typeof body === 'object' && body !== null && 'message' in body
+ ? body.message
+ : typeof body === 'string' && body !== ''
+ ? body
+ : res.statusText
+ throw Object.assign(new Error(message), {
+ url: res.url,
+ statusCode: res.status,
+ statusText: res.statusText,
+ headers: res.headers,
+ body,
+ })
+ }
+
+ return body
+}
+
+function getAuthHeader() {
+ return isAuthenticated()
+ ? { authorization: `Bearer ${localStorage.getItem('token')}` }
+ : {}
+}
+
+export default {
+ get(url, headers) {
+ return fetch(url, {
+ headers: Object.assign(getAuthHeader(), headers),
+ }).then(handleResponse)
+ },
+
+ post(url, body, headers) {
+ const init = {
+ method: 'POST',
+ headers: getAuthHeader(),
+ }
+ if (typeof body === 'object' && body !== null) {
+ init.body = JSON.stringify(body)
+ init.headers['content-type'] = 'application/json; charset=utf-8'
+ }
+ Object.assign(init.headers, headers)
+ return fetch(url, init).then(handleResponse)
+ },
+
+ subscribe(url, callback) {
+ const urlWithToken = new URL(url, location.origin)
+ if (isAuthenticated()) {
+ urlWithToken.searchParams.set('token', localStorage.getItem('token'))
+ }
+ const eventSource = new EventSource(urlWithToken.toString())
+ eventSource.onmessage = ev => {
+ let data
+ try {
+ data = JSON.parse(ev.data)
+ } catch (err) {
+ console.error('could not parse message data as JSON:', err)
+ return
+ }
+ callback(data)
+ }
+ const unsubscribe = () => {
+ eventSource.close()
+ }
+ return unsubscribe
+ },
+}
+```
+
+This module is a wrapper around the [fetch][10] and [EventSource][11] APIs. The most important part is that it adds the JSON web token to the requests.
+
+### Home Page
+
+![home page screenshot][12]
+
+So, when the user login, the home page will be shown. Create a `static/pages/home-page.js` file with the following content:
+
+```
+import { getAuthUser } from '../auth.js'
+import { avatar } from '../shared.js'
+
+export default function homePage() {
+ const authUser = getAuthUser()
+ const template = document.createElement('template')
+ template.innerHTML = `
+
+
+ ${avatar(authUser)}
+ ${authUser.username}
+
+
+
+
+
+ `
+ const page = template.content
+ page.getElementById('logout-button').onclick = onLogoutClick
+ return page
+}
+
+function onLogoutClick() {
+ localStorage.clear()
+ location.reload()
+}
+```
+
+For this post, this is the only content we render on the home page. We show the current authenticated user and a logout button.
+
+When the user clicks to logout, we clear all inside localStorage and do a reload of the page.
+
+### Avatar
+
+That `avatar()` function is to show the user’s avatar. Because it’s used in more than one place, I moved it to a `shared.js` file. Create the file `static/shared.js` with the following content:
+
+```
+export function avatar(user) {
+ return user.avatarUrl === null
+ ? ``
+ : ``
+}
+```
+
+We use a small figure with the user’s initial in case the avatar URL is null.
+
+You can show the initial with a little of CSS using the `attr()` function.
+
+```
+.avatar[data-initial]::after {
+ content: attr(data-initial);
+}
+```
+
+### Development Login
+
+![access page with login form screenshot][13]
+
+In the previous post we coded a login for development. Lets add a form for that in the access page. Go to `static/pages/access-page.js` and modify it a little.
+
+```
+import http from '../http.js'
+
+const template = document.createElement('template')
+template.innerHTML = `
+
Messenger
+
+ Access with GitHub
+`
+
+export default function accessPage() {
+ const page = template.content.cloneNode(true)
+ page.getElementById('login-form').onsubmit = onLoginSubmit
+ return page
+}
+
+async function onLoginSubmit(ev) {
+ ev.preventDefault()
+
+ const form = ev.currentTarget
+ const input = form.querySelector('input')
+ const submitButton = form.querySelector('button')
+
+ input.disabled = true
+ submitButton.disabled = true
+
+ try {
+ const payload = await login(input.value)
+ input.value = ''
+
+ localStorage.setItem('auth_user', JSON.stringify(payload.authUser))
+ localStorage.setItem('token', payload.token)
+ localStorage.setItem('expires_at', payload.expiresAt)
+
+ location.reload()
+ } catch (err) {
+ alert(err.message)
+ setTimeout(() => {
+ input.focus()
+ }, 0)
+ } finally {
+ input.disabled = false
+ submitButton.disabled = false
+ }
+}
+
+function login(username) {
+ return http.post('/api/login', { username })
+}
+```
+
+I added a login form. When the user submits the form. It does a POST requets to `/api/login` with the username. Saves all the data to localStorage and reloads the page.
+
+Remember to remove this form once you are done with the frontend.
+
+* * *
+
+That’s all for this post. In the next one, we’ll continue with the home page to add a form to start conversations and display a list with the latest ones.
+
+[Souce Code][14]
+
+--------------------------------------------------------------------------------
+
+via: https://nicolasparada.netlify.com/posts/go-messenger-access-page/
+
+作者:[Nicolás Parada][a]
+选题:[lujun9972][b]
+译者:[译者ID](https://github.com/译者ID)
+校对:[校对者ID](https://github.com/校对者ID)
+
+本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
+
+[a]: https://nicolasparada.netlify.com/
+[b]: https://github.com/lujun9972
+[1]: https://nicolasparada.netlify.com/posts/go-messenger-schema/
+[2]: https://nicolasparada.netlify.com/posts/go-messenger-oauth/
+[3]: https://nicolasparada.netlify.com/posts/go-messenger-conversations/
+[4]: https://nicolasparada.netlify.com/posts/go-messenger-messages/
+[5]: https://nicolasparada.netlify.com/posts/go-messenger-realtime-messages/
+[6]: https://nicolasparada.netlify.com/posts/go-messenger-dev-login/
+[7]: https://nicolasparada.netlify.com/posts/js-router/
+[8]: https://unpkg.com/@nicolasparada/router
+[9]: https://nicolasparada.netlify.com/img/go-messenger-access-page/access-page.png
+[10]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
+[11]: https://developer.mozilla.org/en-US/docs/Web/API/EventSource
+[12]: https://nicolasparada.netlify.com/img/go-messenger-access-page/home-page.png
+[13]: https://nicolasparada.netlify.com/img/go-messenger-access-page/access-page-v2.png
+[14]: https://github.com/nicolasparada/go-messenger-demo