2017-11-14
Введение.
В приложении представленны примеры, которые демонстрируют использование некоторых Google сервисов, таких как People, GMail и т.д. Само приложение разработано с помощью Nuxt.js. Nuxt.js — это фреймворк для универсальных приложений на Vue.js.
Этот проект на GitHub можно посмотреть здесь
Демонстрацию этого проекта можно посмотреть здесь
Ресурсы
- Google API
- Google API Клиентская Библиотека для JavaScript
- Google People API
- Документация
- Method: people.get
- Method: people.connections.list
- Method: people.get
- Gmail API
- Документация
- Структура электронного сообщения
- RFC 1521 — Почтовый стандарт MIME
- RFC 5322 — Формат сообщений Internet (IMF)
- Структура электронного сообщения
- Nuxt.js
- Документация
- Vue.js
- Vue Router
- Vuex
- Vue.js
Работа приложения
Это приложение демонстрирует работу следующих Google сервисов:
People API
Gmail API
Установка/Конфигурация/Построение приложения
Приложение можно установить с GitHub, варианты построения приложения можно посмотреть в пр.1.
пр.1
# install dependencies
$ npm install # Or yarn install
# environment variables
# Add file "app/env.js" to your project to set user environment variables.
# See the sample file "app/env.example.js".
# serve with hot reload at localhost:3000
$ npm run dev
# build for production and launch server
$ npm run build
$ npm start
# generate static project
$ npm run generate
Структура приложения
Приложение имеет следующую структуру см. пр.2.
пр.2
|--gapi-examples
|-- app/
| |-- assets/ # Un-compiled assets such as Less, Sass or JavaScript
| |-- components/ # Vue.js Components
| |-- config/ # Configuration files
| |-- layouts/ # Application Layouts
| |-- middleware/ # Application Middleware (functions that can be run before rendering)
| |-- pages/ # Application Views and Routes
| |-- plugins/ # Javascript plugins
| |-- static/ # Static files. (each file inside this directory is mapped to "/")
| |-- store/ # Vuex Store files
| |-- env.example.js # Example of env.js (user configuration file)
| `-- env.js # User configuration file
|
|-- docs/ # folder created with the command ($ npm run generate)
|-- node_modules/ # folder created with the command ($ npm install # Or yarn install)
|-- .gitignore # Git ignore file
|-- LICENSE.md # License file
|-- nuxt.config.js # Nuxt configuration file
|-- package.json # NPM configuration file
`-- README.md # Readme file
Загрузка/Инициализация клиенской библиотеки Google API
Вначале нужно загрузить и инициализировать клиенскую библиотеку Google API см. пр.3. Загрузка происходит с помощью функции loadGapi()
при создании компонента в модуле /app/layouts/default.vue
.
...
/**
* Google Client load/init
* @param params (Object)
* etc. {
* apiKey: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
* clientId: 'xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com',
* discoveryDocs: [ 'https://people.googleapis.com/$discovery/rest?version=v1',
* 'https://www.googleapis.com/discovery/v1/apis/gmail/v1/rest'],
* scope: 'profile
* https://www.googleapis.com/auth/contacts.readonly
* https://www.googleapis.com/auth/gmail.readonly
* https://www.googleapis.com/auth/gmail.send'
* }
* @return {Promise}
*/
loadClient (params) {
return new Promise(function (resolve, reject) {
_loadGoogleApi().then(function () {
if (debug) {
console.log('loadGoogleAPI - OK')
}
return _initClient(params)
}).then(function () {
if (debug) {
console.log('googleClient.init - OK')
}
// Load gmail library
window.gapi.client.load('gmail', 'v1', resolve)
})
})
}
/**
* Load google api
* @return {Promise}
* @private
*/
_loadGoogleApi () {
return new Promise(function (resolve, reject) {
const script = document.createElement('script')
script.src = 'https://apis.google.com/js/platform.js'
script.onreadystatechange = script.onload = function () {
if (!script.readyState || /loaded|complete/.test(script.readyState)) {
setTimeout(function () {
resolve()
}, 500)
}
}
document.getElementsByTagName('head')[0].appendChild(script)
})
}
/**
* Google client load/init
* @param params (Object)
* etc. {
* apiKey: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
* clientId: 'xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com',
* discoveryDocs: [ 'https://people.googleapis.com/$discovery/rest?version=v1',
* 'https://www.googleapis.com/discovery/v1/apis/gmail/v1/rest'],
* scope: 'profile
* https://www.googleapis.com/auth/contacts.readonly
* https://www.googleapis.com/auth/gmail.readonly
* https://www.googleapis.com/auth/gmail.send'
* }
* @return {Promise}
* @private
*/
_initClient (params) {
return new Promise(function (resolve, reject) {
// Client Init
const initClient = function () {
window.gapi.client.init(params).then(() => {
resolve()
}, (error) => {
console.error('gapi.client.init - Error', error)
alert(`gapi.client.init - Error: ${error.error}\n Details: ${error.details}`)
})
}
window.gapi.load('client:auth2', initClient)
})
}
...
Авторизация пользователя на Google сервере
В приложении авторизация пользователя происходит на странице сайта Login
. Без авторизации пользователя доступ к демонстрационным примерам - запрещен! Авторизация пользователя происходит в модуле /app/pages/login.vue
с помощью функции GoogleAuth.signIn(), см. пр.4.
...
/**
* Google SignIn
* @param successCallback (Function)
* @param errorCallback (Function)
*/
signIn (successCallback, errorCallback) {
window.gapi.auth2.getAuthInstance().signIn().then(function (googleUser) {
successCallback(googleUser)
if (debug) {
console.log('GoogleAuth.signIn - OK')
}
}, function (error) {
errorCallback(error)
console.log('GoogleAuth.signIn - Error: ', error)
})
}
...
Google People API (Method: people.get)
В приложении эта операция выполняется в модуле /app/pages/services/people-get.vue
по адресу /services/people-get
При этом мы можем получить иноформацию для конкретного пользователя по его уникальному имени. Используя имя people/me
мы получим информацию о самом авторизированном пользователе см. пр.5.
...
getMyNames() {
return new Promise((resolve, reject) => {
window.gapi.client.people.people.get({
'resourceName': 'people/me',
'personFields': 'names,emailAddresses'
}).then((resp) => {
const names = resp.result.names[0]
if (debug) {
console.log('api.people.get - Executed: ')
}
resolve(names)
}, (error) => {
console.log('people.get - Error. ', `Error: ${error}`)
alert(`Error: ${error}`)
})
})
}
...
Google People API (Method: people.connections.list)
В приложении эта операция выполняется в модуле /app/pages/services/people-connections.vue
по адресу /services/people-connections
Обеспечивает список авторизированных пользовательских контактов, и их профайлов см. пр.6.
...
getMyConnections() {
let myConnections = []
return new Promise((resolve, reject) => {
window.gapi.client.people.people.connections.list({
'resourceName': 'people/me',
'pageSize': 10,
'personFields': 'names,emailAddresses'
}).then(function (response) {
const connections = response.result.connections
if (connections.length > 0) {
for (let i = 0; i < connections.length; i++) {
const person = connections[i]
if (person.names && person.names.length > 0) {
myConnections.push(person.names[0].displayName)
} else {
myConnections.push('No display name found for connection.')
}
}
} else {
myConnections.push('No upcoming events found.')
}
if (debug) {
console.log('api.people.connections - Executed')
}
resolve(myConnections)
}, (error) => {
console.log('people.connections - Error. ', `Error: ${error}`)
alert(`Error: ${error}`)
})
})
}
...
Передача email сообщений (Method: users.messages: send)
В приложении эта операция выполняется в модуле /app/pages/services/gmail-send-message.vue
по адресу /services/gmail-send-message
Этот метод отправляет указанное сообщение получателям в To, Cc, Bcc
заголовках. см. пр.7.
...
/**
* Send email
* @param params
* etc.{
userId: 'me',
to: my@test.com,
subject: Request for my resume from the employer,
message: 'My Message!',
callback: function () {
....
}
* }
*/
send (params) {
try {
const headers = {
'To': params.to,
'Subject': params.subject,
'Content-Type': 'text/html; charset="UTF-8"'
}
let email = ''
_.forEach(headers, function (value, key) {
email += `${key}: ${value}` + '\r\n'
})
email += '\r\n' + params.message
const base64EncodedEmail = _b64UrlEncodeUnicode(email)
const sendRequest = window.gapi.client.gmail.users.messages.send({
'userId': params.userId,
'resource': {
'raw': base64EncodedEmail
}
})
sendRequest.execute(params.callback)
} catch (e) {
throw e
}
}
/**
* To encode the unicode string into Base64-encoded-url value
*
* @param str
* @return {string}
*/
_b64UrlEncodeUnicode (str) {
// first we use encodeURIComponent to get percent-encoded UTF-8,
// then we convert the percent encodings into raw bytes which
// can be fed into btoa.
const b64Encoded = btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
function toSolidBytes (match, p1) {
return String.fromCharCode('0x' + p1)
}))
// Replace for URL
return b64Encoded.replace(/\+/g, '-').replace(/\//g, '_')
}
...
Получение сообщений в формате (text/html; charset="UTF-8")
В приложении эта операция выполняется в модуле /app/pages/services/gmail-display-inbox.vue
по адресу /services/gmail-display-inbox
Для получения сообщений в формате (text/html; charset="UTF-8") используются команды (Users.messages: list/get) см. пр.8.
...
/**
* Get inbox messages
* @param params
* etc. {
userId: 'me',
labelIds: 'INBOX',
maxResults: 10
* }
* @return {Promise}
*/
getInbox (params) {
let arrPromises = []
return new Promise((resolve, reject) => {
_getMyMessagesList(params)
.then(list => {
_.forEach(list, function (item) {
arrPromises.push(_getMessageForId(item.id, params.userId))
})
const allPromises = Promise.all(arrPromises)
resolve(allPromises)
})
})
}
_getMyMessagesList (params) {
// Execute this request for 'gmail.users.messages.list'
const request = window.gapi.client.gmail.users.messages.list(params)
return new Promise((resolve, reject) => {
request.execute(function (response) {
if (debug) {
console.log('api.gmail.users.messages.list - Executed: ', response.messages)
}
resolve(response.messages)
})
})
}
_getMessageForId (id, userId) {
let _message = {}
// Execute this request for 'gmail.users.messages.get'
const messageRequest = window.gapi.client.gmail.users.messages.get({
'userId': userId,
'id': id
})
return new Promise((resolve, reject) => {
messageRequest.execute(message => {
// Parsing message
_message.id = message.id
_message.from = _getHeader(message.payload.headers, 'From')
_message.subject = _getHeader(message.payload.headers, 'Subject')
_message.date = _getHeader(message.payload.headers, 'Date')
_message.reply_to = _getHeader(message.payload.headers, 'Reply-to')
_message.message_id = _getHeader(message.payload.headers, 'Message-ID')
_message.body = _getBody(message.payload)
if (debug) {
console.log('api.gmail.users.messages.get - Executed: ', _message)
}
resolve(_message)
})
})
}
_getHeader (headers, index) {
let headerValue = ''
_.forEach(headers, function (header) {
if (header.name.toLowerCase() === index.toLowerCase()) {
headerValue = header.value
}
})
return headerValue
}
_getBody (message) {
var encodedBody = ''
try {
if (typeof message.parts === 'undefined') {
encodedBody = message.body.data
} else {
encodedBody = _getHTMLPart(message.parts)
}
return _b64UrlDecodeUnicode(encodedBody)
} catch (error) {
console.error('apiGmail._getBody - Error', error)
throw error
}
}
_getHTMLPart (arr) {
for (let x = 0; x <= arr.length; x++) {
if (typeof arr[x].parts === 'undefined') {
if (arr[x].mimeType === 'text/html') {
return arr[x].body.data
}
} else {
return _getHTMLPart(arr[x].parts)
}
}
return ''
}
/**
* To decode the Base64-encoded-url value back into a String
*
* @param str
* @return {string}
*/
_b64UrlDecodeUnicode (str) {
const encodedBody = str.replace(/-/g, '+').replace(/_/g, '/').replace(/\s/g, '')
// Going backwards: from bytestream, to percent-encoding, to original string.
return decodeURIComponent(atob(encodedBody).split('').map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
}).join(''))
}
...