-
Notifications
You must be signed in to change notification settings - Fork 77
/
Plugins.js
220 lines (184 loc) · 6.55 KB
/
Plugins.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/* eslint-disable no-undef */
'use strict'
const puppeteer = require('puppeteer')
/**
*
* @param {options.username} string username
* @param {options.password} string password
* @param {options.loginUrl} string password
* @param {options.args} array[string] string array which allows providing further arguments to puppeteer
* @param {options.loginSelector} string a selector on the loginUrl page for the social provider button
* @param {options.loginSelectorDelay} number delay a specific amount of time before clicking on the login button, defaults to 250ms. Pass a boolean false to avoid completely.
* @param {options.postLoginSelector} string a selector on the app's post-login return page to assert that login is successful
* @param {options.preLoginSelector} string a selector to find and click on before clicking on the login button (useful for accepting cookies)
* @param {options.headless} boolean launch puppeteer in headless more or not
* @param {options.logs} boolean whether to log cookies and other metadata to console
* @param {options.getAllBrowserCookies} boolean whether to get all browser cookies instead of just for the loginUrl
* @param {options.isPopup} boolean is your google auth displayed like a popup
* @param {options.popupDelay} number delay a specific milliseconds before popup is shown. Pass a falsy (false, 0, null, undefined, '') to avoid completely
* @param {options.cookieDelay} number delay a specific milliseconds before get a cookies. Pass a falsy (false, 0, null, undefined, '') to avoid completely.
*
*/
module.exports.GoogleSocialLogin = async function GoogleSocialLogin(options = {}) {
validateOptions(options)
const launchOptions = {headless: !!options.headless}
if (options.args && options.args.length) {
launchOptions.args = options.args
}
const browser = await puppeteer.launch(launchOptions)
let page = await browser.newPage()
let originalPageIndex = 1
await page.setViewport({width: 1280, height: 800})
await page.setExtraHTTPHeaders({
'Accept-Language': 'en-US;q=0.9,en;q=0.8'
})
await page.setUserAgent(
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
)
await page.goto(options.loginUrl)
await login({page, options})
// Switch to Popup Window
if (options.isPopup) {
if (options.popupDelay) {
await delay(options.popupDelay)
}
const pages = await browser.pages()
// remember original window index
originalPageIndex = pages.indexOf(
pages.find(p => page._target._targetId === p._target._targetId)
)
page = pages[pages.length - 1]
}
await typeUsername({page, options})
await typePassword({page, options})
// Switch back to Original Window
if (options.isPopup) {
if (options.popupDelay) {
await delay(options.popupDelay)
}
const pages = await browser.pages()
page = pages[originalPageIndex]
}
if (options.cookieDelay) {
await delay(options.cookieDelay)
}
const cookies = await getCookies({page, options})
const lsd = await getLocalStorageData({page, options})
const ssd = await getSessionStorageData({page, options})
await finalizeSession({page, browser, options})
return {
cookies,
lsd,
ssd
}
}
function delay(time) {
return new Promise(function(resolve) {
setTimeout(resolve, time)
})
}
function validateOptions(options) {
if (!options.username || !options.password) {
throw new Error('Username or Password missing for social login')
}
}
async function login({page, options} = {}) {
if (options.preLoginSelector) {
await page.waitForSelector(options.preLoginSelector)
await page.click(options.preLoginSelector)
}
await page.waitForSelector(options.loginSelector)
if (options.loginSelectorDelay !== false) {
await delay(options.loginSelectorDelay)
}
await page.click(options.loginSelector)
}
async function typeUsername({page, options} = {}) {
await page.waitForSelector('input[type="email"]')
await page.type('input[type="email"]', options.username)
await page.click('#identifierNext')
}
async function typePassword({page, options} = {}) {
let buttonSelectors = ['#signIn', '#passwordNext', '#submit']
await page.waitForSelector('input[type="password"]', {visible: true})
await page.type('input[type="password"]', options.password)
const buttonSelector = await waitForMultipleSelectors(buttonSelectors, {visible: true}, page)
await page.click(buttonSelector)
}
async function getCookies({page, options} = {}) {
await page.waitForSelector(options.postLoginSelector)
const cookies = options.getAllBrowserCookies
? await getCookiesForAllDomains(page)
: await page.cookies(options.loginUrl)
if (options.logs) {
console.log(cookies)
}
return cookies
}
async function getLocalStorageData({page, options} = {}) {
await page.waitForSelector(options.postLoginSelector)
const localStorageData = await page.evaluate(() => {
let json = {}
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i)
json[key] = localStorage.getItem(key)
}
return json
})
if (options.logs) {
console.log(localStorageData)
}
return localStorageData
}
async function getSessionStorageData({page, options} = {}) {
await page.waitForSelector(options.postLoginSelector)
const sessionStorageData = await page.evaluate(() => {
let json = {}
for (let i = 0; i < sessionStorage.length; i++) {
const key = sessionStorage.key(i)
json[key] = sessionStorage.getItem(key)
}
return json
})
if (options.logs) {
console.log(sessionStorageData)
}
return sessionStorageData
}
async function getCookiesForAllDomains(page) {
const cookies = await page._client.send('Network.getAllCookies', {})
return cookies.cookies
}
async function finalizeSession({page, browser, options} = {}) {
await browser.close()
}
async function waitForMultipleSelectors(selectors, options, page) {
const navigationOutcome = await racePromises(
selectors.map(selector => page.waitForSelector(selector, options))
)
return selectors[parseInt(navigationOutcome)]
}
async function racePromises(promises) {
const wrappedPromises = []
let resolved = false
promises.map((promise, index) => {
wrappedPromises.push(
new Promise(resolve => {
promise.then(
() => {
resolve(index)
},
error => {
if (!resolved) {
throw error
}
}
)
})
)
})
return Promise.race(wrappedPromises).then(index => {
resolved = true
return index
})
}