Socket
Socket
Sign inDemoInstall

homebridge-nest-cam2

Package Overview
Dependencies
3
Maintainers
1
Versions
33
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.0.26 to 0.0.27

77

index.js

@@ -6,3 +6,7 @@ 'use strict';

const NestConnection = require('./lib/nest-connection.js');
const Promise = require('bluebird');
Promise.delay = function(time_ms) {
return new Promise(resolve => setTimeout(resolve, time_ms));
}
const modelTypes = {

@@ -26,23 +30,20 @@ 8: 'Nest Cam Indoor',

const setupConnection = function(config, log) {
return new Promise(function (resolve, reject) {
if (!config.access_token && !config.googleAuth && (!config.email || !config.password)) {
reject('You did not specify your Nest account credentials {\'email\',\'password\'}, or an access_token, or googleAuth, in config.json');
return;
}
if (config.googleAuth && (!config.googleAuth.issueToken || !config.googleAuth.cookies || !config.googleAuth.apiKey)) {
reject('When using googleAuth, you must provide issueToken, cookies and apiKey in config.json. Please see README.md for instructions');
const setupConnection = async function(config, log) {
if (!config.googleAuth) {
reject('You did not specify your Google account credentials, googleAuth, in config.json');
return;
}
}
const conn = new NestConnection(config, log);
conn.auth().then(connected => {
if (connected) {
resolve(conn);
} else {
reject('Unable to connect to Nest service.');
}
});
});
if (config.googleAuth && (!config.googleAuth.issueToken || !config.googleAuth.cookies || !config.googleAuth.apiKey)) {
reject('You must provide issueToken, cookies and apiKey in config.json. Please see README.md for instructions');
return;
}
const conn = new NestConnection(config, log);
try {
let connected = await conn.auth();
return connected;
} catch(error) {
throw('Unable to connect to Nest service.', error);
}
};

@@ -73,5 +74,14 @@

*/
addCameras(accessToken) {
async addCameras(accessToken) {
let self = this;
self.nestAPI = new Nest(accessToken, self.config, self.log);
// Nest needs to be reauthenticated about every hour
setInterval(async function() {
let connected = await setupConnection(self.config, self.log);
if (connected) {
self.nestAPI.accessToken = self.config.access_token;
}
}, 3600000);
self.nestAPI.on('cameras', (cameras) => {

@@ -101,4 +111,4 @@ let configuredAccessories = [];

.getCharacteristic(Characteristic.On)
.on('set', function(value, callback) {
camera.toggleActive(value);
.on('set', async function(value, callback) {
await camera.toggleActive(value);
self.log.info("Setting %s to %s", accessory.displayName, (value ? 'on' : 'off'));

@@ -112,6 +122,6 @@ callback();

});
self.nestAPI.fetchCameras();
await self.nestAPI.fetchCameras();
}
didFinishLaunching() {
async didFinishLaunching() {
let self = this;

@@ -123,16 +133,7 @@ let googleAuth = self.config['googleAuth'];

}
setupConnection(self.config, self.log)
.then(function(conn){
return;
})
.then(function(data) {
self.addCameras(self.config.access_token);
})
.catch(function(err) {
self.log.error(err);
if (callback) {
callback([]);
}
});
let connected = await setupConnection(self.config, self.log);
if (connected) {
await self.addCameras(self.config.access_token);
}
}
}

@@ -6,4 +6,3 @@ /**

const Promise = require('bluebird');
const rp = require('request-promise');
const axios = require('axios');

@@ -28,97 +27,65 @@ 'use strict';

function Connection(config, log) {
this.config = config;
this.log = log;
this.token = '';
this.config = config;
this.log = log;
}
Connection.prototype.auth = function() {
return new Promise(resolve => {
Promise.coroutine(function* () {
let req, body;
Connection.prototype.auth = async function() {
let req, body;
this.connected = false;
this.token = null;
//Only doing google auth from now on
let issueToken = this.config.googleAuth.issueToken;
let cookies = this.config.googleAuth.cookies;
//Only doing google auth from now on
let issueToken = this.config.googleAuth.issueToken;
let cookies = this.config.googleAuth.cookies;
this.log.debug('Authenticating via Google.');
req = {
method: 'GET',
followAllRedirects: true,
timeout: API_TIMEOUT_SECONDS * 1000,
uri: issueToken,
headers: {
'Sec-Fetch-Mode': 'cors',
'User-Agent': USER_AGENT_STRING,
'X-Requested-With': 'XmlHttpRequest',
'Referer': 'https://accounts.google.com/o/oauth2/iframe',
'cookie': cookies
},
json: true
};
let result = yield rp(req);
let googleAccessToken = result.access_token;
req = {
method: 'POST',
followAllRedirects: true,
timeout: API_TIMEOUT_SECONDS * 1000,
uri: 'https://nestauthproxyservice-pa.googleapis.com/v1/issue_jwt',
body: {
embed_google_oauth_access_token: true,
expire_after: '3600s',
google_oauth_access_token: googleAccessToken,
policy_id: 'authproxy-oauth-policy'
},
headers: {
'Authorization': 'Bearer ' + googleAccessToken,
'User-Agent': USER_AGENT_STRING,
'x-goog-api-key': this.config.googleAuth.apiKey,
'Referer': 'https://home.nest.com'
},
json: true
};
result = yield rp(req);
this.config.access_token = result.jwt;
if (this.config.access_token && this.config.googleAuth) {
req = {
method: 'GET',
followAllRedirects: true,
timeout: API_TIMEOUT_SECONDS * 1000,
uri: URL_NEST_AUTH,
headers: {
'Authorization': 'Basic ' + this.config.access_token,
'User-Agent': USER_AGENT_STRING
},
json: true
};
} else {
resolve(false);
return;
}
try {
body = yield rp(req);
this.connected = true;
this.token = body.access_token;
this.transport_url = body.urls.transport_url;
this.userid = body.userid;
resolve(true);
} catch(error) {
this.connected = false;
if (error.statusCode == 400) {
this.log.error('Auth failed: access token specified in Homebridge configuration rejected');
resolve(false);
} else if (error.statusCode == 429) {
this.log.error('Auth failed: rate limit exceeded. Please try again in 60 minutes');
resolve(false);
} else {
this.log.error('Could not authenticate with Nest (code ' + (error.statusCode || (error.cause && error.cause.code)) + '). Retrying in ' + API_AUTH_FAIL_RETRY_DELAY_SECONDS + ' second(s).');
Promise.delay(API_AUTH_FAIL_RETRY_DELAY_SECONDS * 1000).then(() => this.auth()).then(connected => resolve(connected));
}
}
}).call(this);
});
this.log.debug('Authenticating via Google.');
let result;
try {
req = {
method: 'GET',
timeout: API_TIMEOUT_SECONDS * 1000,
url: issueToken,
headers: {
'Sec-Fetch-Mode': 'cors',
'User-Agent': USER_AGENT_STRING,
'X-Requested-With': 'XmlHttpRequest',
'Referer': 'https://accounts.google.com/o/oauth2/iframe',
'cookie': cookies
}
};
result = (await axios(req)).data;
let googleAccessToken = result.access_token;
if (result.error) {
this.log.error('Google authentication was unsuccessful. Make sure you did not log out of your Google account after getting your googleAuth parameters.');
throw(result);
}
req = {
method: 'POST',
timeout: API_TIMEOUT_SECONDS * 1000,
url: 'https://nestauthproxyservice-pa.googleapis.com/v1/issue_jwt',
data: {
embed_google_oauth_access_token: true,
expire_after: '3600s',
google_oauth_access_token: googleAccessToken,
policy_id: 'authproxy-oauth-policy'
},
headers: {
'Authorization': 'Bearer ' + googleAccessToken,
'User-Agent': USER_AGENT_STRING,
'x-goog-api-key': this.config.googleAuth.apiKey,
'Referer': 'https://home.nest.com'
}
};
result = (await axios(req)).data;
this.config.access_token = result.jwt;
return true;
} catch(error) {
error.status = error.response && error.response.status;
this.log.error('Access token acquisition via googleAuth failed (code ' + (error.status || error.code) + ').');
if (['ECONNREFUSED','ESOCKETTIMEDOUT','ECONNABORTED','ENOTFOUND','ENETUNREACH'].includes(error.code)) {
this.log.error('Retrying in ' + API_AUTH_FAIL_RETRY_DELAY_SECONDS + ' second(s).');
await Promise.delay(API_AUTH_FAIL_RETRY_DELAY_SECONDS * 1000);
return await this.auth();
} else {
return false;
}
}
};
'use strict';
const https = require('https');
const querystring = require('querystring');
const axios = require('axios');
const EventEmitter = require('events');
const NestCam = require('./nestcam').NestCam;
const NestConnection = require('./nest-connection.js');
const NestAPIHostname = 'https://webapi.camera.home.nest.com';
const NestAPIHostname = 'webapi.camera.home.nest.com';
const NestAuthAPIHostname = 'home.nest.com';
class NestAPI extends EventEmitter {

@@ -18,76 +15,25 @@ constructor(accessToken, config, log) {

self.log = log;
// Nest needs to be reauthenticated about every hour
const interval = setInterval(() => {
self.reauth(config, log)
.then(function(conn){
return;
})
.then(function(data) {
self.accessToken = config.access_token;
})
.catch(function(err) {
self.log.error(err);
if (callback) {
callback([]);
}
});
}, 3600000);
}
/**
* Reauthenticate the google access user_token
* @param config The configuration object
* @param log The logger
*/
reauth(config, log) {
return new Promise(function (resolve, reject) {
let self = this;
const conn = new NestConnection(config, log);
conn.auth().then(connected => {
if (connected) {
resolve(conn);
} else {
reject('Unable to connect to Nest service.');
}
});
});
};
/**
* Fetch cameras from nest and add them to Homebridge
*/
fetchCameras() {
async fetchCameras() {
let self = this;
self.sendHomeRequest('/api/cameras.get_owned_and_member_of_with_properties', 'GET')
.then((response) => {
let text = response.toString();
let json = JSON.parse(text);
if (json.status === 0) {
var cameras = [];
json.items.forEach((cameraInfo) => {
let camera = new NestCam(self, cameraInfo, self.log);
cameras.push(camera);
});
self.emit('cameras', cameras);
} else {
self.log.error('Failed to load cameras. ' + json.status_detail);
}
})
.catch((err) => {
self.log.error('Failed to load cameras. ' + err.message);
});
let response = await self.sendRequest(NestAPIHostname, '/api/cameras.get_owned_and_member_of_with_properties', 'GET');
if (response) {
if (response.status === 0) {
var cameras = [];
response.items.forEach((cameraInfo) => {
let camera = new NestCam(self, cameraInfo, self.log);
cameras.push(camera);
});
self.emit('cameras', cameras);
} else {
self.log.error('Failed to load cameras. ' + response.status_detail);
}
}
}
/**
* Send api request to the camera endpoint
* @param endpoint The endpoint to send the request
* @param method Usually "GET" or "POST"
* @param body The body of the request or null if a "GET"
*/
sendHomeRequest(endpoint, method, body) {
let self = this;
return self.sendRequest(NestAPIHostname, endpoint, method, body);
}
/**
* Send a generic api request

@@ -99,42 +45,29 @@ * @param hostname The base uri to send the request

*/
sendRequest(hostname, endpoint, method, body) {
async sendRequest(hostname, endpoint, method, body) {
let self = this;
let headers = {
'User-Agent': 'iPhone iPhone OS 11.0 Dropcam/5.14.0 com.nestlabs.jasper.release Darwin',
'Referer': 'https://home.nest.com/'
};
return new Promise((resolve, reject) => {
let headers = {
'User-Agent': 'iPhone iPhone OS 11.0 Dropcam/5.14.0 com.nestlabs.jasper.release Darwin',
'Referer': 'https://home.nest.com/'
};
if (method === 'POST') {
headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
}
if (method === 'POST') {
headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
}
if (self.accessToken !== undefined) {
headers['Cookie'] = 'user_token=' + self.accessToken;
}
if (self.accessToken !== undefined) {
headers['Cookie'] = 'user_token=' + self.accessToken;
}
let options = {
hostname: hostname,
path: endpoint,
try {
let req = {
method: method,
url: hostname + endpoint,
data: body,
headers: headers
};
let req = https.request(options, (res) => {
if (res.statusCode < 200 || res.statusCode >= 300) {
let error = new Error('Unexpected API Error - ' + res.statusCode);
error.code = res.statusCode;
reject(error);
}
const resBody = [];
res.on('data', (chunk) => resBody.push(chunk));
res.on('end', () => resolve(Buffer.concat(resBody)));
});
req.on('error', (err) => reject(err));
if (body !== undefined) {
req.write(body);
}
req.end();
});
return (await axios(req)).data;
} catch(error) {
error.status = error.response && error.response.status;
self.log.error('Unexpected API Error - ' + (error.status || error.code));
}
}

@@ -141,0 +74,0 @@ }

@@ -5,4 +5,2 @@ 'use strict';

const fs = require('fs');
const ip = require('ip');
const spawn = require('child_process').spawn;

@@ -12,2 +10,3 @@ const querystring = require('querystring');

const ModelTypes = require('./protos/ModelTypes.js').ModelTypes;
const NestAPIHostname = 'https://webapi.camera.home.nest.com';

@@ -28,6 +27,6 @@ class NestCam {

self.nexusTalkHost = info.direct_nexustalk_host;
self.apiHost = info.nexus_api_http_server.slice(8); // remove https://
self.apiHost = info.nexus_api_http_server;
}
toggleActive(enabled) {
async toggleActive(enabled) {
let self = this;

@@ -38,9 +37,4 @@ let query = querystring.stringify({

});
self.api.sendHomeRequest('/api/dropcams.set_properties', 'POST', query)
.then((response) => {
self.enabled = enabled;
})
.catch((err) => {
self.log.error(err);
});
let response = await self.api.sendRequest(NestAPIHostname, '/api/dropcams.set_properties', 'POST', query);
self.enabled = enabled;
}

@@ -84,6 +78,3 @@

let self = this;
// This is for backward compatibility with the old useOMX config value
if (config.useOMX) {
self.ffmpegCodec = "h264_omx";
} else if (config.ffmpegCodec) {
if (config.ffmpegCodec) {
self.ffmpegCodec = config.ffmpegCodec;

@@ -156,3 +147,3 @@ }

handleSnapshotRequest(request, callback) {
async handleSnapshotRequest(request, callback) {
let self = this;

@@ -163,9 +154,4 @@ let query = querystring.stringify({

});
self.api.sendRequest(self.apiHost, '/get_image?' + query, 'GET')
.then((response) => {
callback(undefined, response);
})
.catch((err) => {
callback(err);
});
let response = await self.api.sendRequest(self.apiHost, '/get_image?' + query, 'GET')
callback(undefined, response);
}

@@ -172,0 +158,0 @@

{
"name": "homebridge-nest-cam2",
"version": "0.0.26",
"version": "0.0.27",
"description": "Nest cam plugin for homebridge: https://homebridge.io/",

@@ -23,6 +23,4 @@ "license": "ISC",

"pbf": "^3.1.0",
"bluebird": "^3.5.4",
"request": "^2.88.0",
"request-promise": "^4.2.4"
"axios": "^0.19.2"
}
}

@@ -5,2 +5,6 @@ # homebridge-nest-cam2

[![NPM](https://nodei.co/npm/homebridge-nest-cam2.png?compact=true)](https://nodei.co/npm/homebridge-nest-cam2/)
[![Beerpay](https://beerpay.io/Brandawg93/homebridge-nest-cam2/badge.svg)](https://beerpay.io/Brandawg93/homebridge-nest-cam2)
![npm](https://img.shields.io/npm/dt/homebridge-nest-cam2)
## Notes

@@ -10,12 +14,2 @@ - This is a continuation of the previous [homebridge-nest-cam](https://github.com/KhaosT/homebridge-nest-cam) plugin.

## Changelog
| Date | Version | Description |
|---------|---------|-------------------------------|
| 1/24/20 | 0.20.0 | Initial Commit |
| 1/24/20 | 0.22.0 | Zero Latency Tune |
| 1/25/20 | 0.23.0 | Fix Google Reauthentication |
| 1/26/20 | 0.24.0 | Nest Hello Resolution Support |
| 1/28/20 | 0.25.0 | Toggle Streaming |
| 2/05/20 | 0.26.0 | Performance Improvements |
## FAQ

@@ -22,0 +16,0 @@ Q: Why is there no audio?

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc