Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 84 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
![License](https://img.shields.io/npm/l/node-blink-security.svg)

# node-blink-security
This is a Node.js version of [this python library](https://github.com/fronzbot/blinkpy). It allows to communicate with Blink Home Security System from a Node.js application.
This is a Node.js version of [this python library](https://github.com/fronzbot/blinkpy). It allows communication with Blink Home Security System from a Node.js application.

# Installation
```
Expand All @@ -16,36 +16,109 @@ npm install node-blink-security

# Usage

Logging in with a valid `username` and `password` will generate a 2FA verification text or email from Blink. You will be prompted via the command line for the 2FA code. Once successfully verified, you will receive an OAuth token and refresh token information that can be stored and reused for future Blink access to avoid needing to re-verify each time.

```javascript
const Blink = require('node-blink-security');
const fs = require("fs");
const {BlinkAuth, Blink} = require('node-blink-security');

const blinkAuth = new BlinkAuth({username: 'YOUR_EMAIL', password: 'YOUR_PASSWORD', deviceId:'DEVICE_ID'});
const blink = new Blink(blinkAuth);

var blink = new Blink('YOUR_EMAIL', 'YOUR_PASSWORD', 'DEVICE_ID');
const blink = new Blink();
blink.setupSystem()
.then(() => {
// save login creds for next time
const loginAttributes = blinkAuth.getLoginAttributes();
console.log('new tokens', loginAttributes);

const jsonString = JSON.stringify(loginAttributes, null, 2);
fs.writeFile('creds.json', jsonString, (err) => {
if (err) {
console.error('Error writing JSON file:', err);
} else {
console.log('JSON data successfully written to creds.json');
}
});

blink.setArmed()
.then(() => {
// see the object dump for details
console.log(blink);
console.log('blink');
});
}, (error) => {
console.log(error);
});
```

Previously stored login credentials from `getLoginCredentials` can be reused like this:

```javascript
const {BlinkAuth, Blink} = require('node-blink-security');
const fs = require("fs");
const creds = require("./creds.json");

const blinkAuth = new BlinkAuth(creds, true /* noPrompt */);
const blink = new Blink(blinkAuth);

blink.setupSystem("Cary Outside").then(
() => {
blink.setArmed()
.then(() => {
// see the object dump for details
console.log(blink);
});
},
(error) => {
console.log(error);
}
);
```

Tokens expire after 4 hours. The `BlinkAuth` will automatically renew the access token when it expires. You can provide a callback to `BlinkAuth` to be notified when tokens are refreshed and store the new tokens like so:

```javascript
const {BlinkAuth, Blink} = require('node-blink-security');
const fs = require("fs");
const creds = require("./creds.json");

const blinkAuth = new BlinkAuth(creds, true, callback= () => {
// callback when tokens are refreshed
const loginAttributes = blinkAuth.getLoginAttributes();
console.log('refreshed tokens', loginAttributes);

const jsonString = JSON.stringify(loginAttributes, null, 2);
fs.writeFile('creds.json', jsonString, (err) => {
if (err) {
console.error('Error writing JSON file:', err);
} else {
console.log('JSON data successfully written to creds.json');
}
});
});
```

# API

```javascript
class Blink
class BlinkAuth(loginData, noPrompt=false, callback=null)
```

## Constructor
* `email` - your Blink account email
Holds authentication information for the Blink account. If no `loginData` is provided, you will be prompted for `username` and `password`. If `username` and `password` are provided, you will be prompted for a 2FA code. If you supply the results of `getLoginAttributes()` from a prior session and specify `noPrompt=true`, the prior authentication information will be used for accessing the Blink API. Finally, if you provide a `callback` function, it will be called whenever the login attributes are refreshed, allowing you to store the new tokens for future use.

## loginData

* `username` - your Blink account email
* `password` - your Blink account password
* `deviceId` - identifies your device and registers it in your account. It's required since version 4.0.0 of this package as this is when Blink switched to 2-factor authentication flow. The value is provided by you and it should let you identify the device correctly when you receive a verification email from Blink.
* `options`
* * `auth_2FA: false` - set to `true` if you want to receive verification code for each login, otherwise you'll receive verification email only once for the first time and after that the device will be remembered by Blink.
* * `verification_timeout: 60000` - number of milliseconds to wait for email verification until retrying account login
* * `device_name: "node-blink-security"` - this name appears in verification email along with your `deviceId`

## Properties

* `blinkAuth.getLoginAttributes` - returns the current login attributes (token, refresh token, token expiration, etc.) that can be stored for future use

```javascript
class Blink
```

## Properties

Expand Down
3 changes: 2 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
*/

const Blink = require('./lib/blink');
const BlinkAuth = require('./lib/blink_auth');

module.exports = Blink;
module.exports = { Blink, BlinkAuth };
171 changes: 171 additions & 0 deletions lib/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@

const Logger = require("./logger");

/* auth API functions */
const requestLogin = (
auth,
url,
loginData,
isRefresh
) => {
const headers = {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": DEFAULT_USER_AGENT,
hardware_id: loginData.deviceId || "Blink-Home",
};

// Add 2FA code to headers if provided
if ("twoFACode" in loginData) {
headers["2fa-code"] = loginData.twoFACode;
}

// Prepare form data for OAuth
const formData = {
username: loginData.username,
client_id: OAUTH_CLIENT_ID,
scope: OAUTH_SCOPE,
};

if (isRefresh) {
formData.grant_type = OAUTH_GRANT_TYPE_REFRESH_TOKEN;
formData.refresh_token = auth.refreshToken;
} else {
formData.grant_type = OAUTH_GRANT_TYPE_PASSWORD;
formData.password = loginData.password;
}

const formParams = new URLSearchParams(formData);
const data = formParams.toString();

return auth.post(
url,
headers,
data,
false, // not json
true // skipRefreshCheck
);
};

const requestTier = (auth, url) => {
const loginData = auth.getLoginAttributes();

const headers = {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": DEFAULT_USER_AGENT,
Authorization: `Bearer ${loginData.token}`,
};

return auth.get(url, headers, false, true);
};

/* blink API functions */
const getIDs = (blink) => {
return httpGet(blink, blink.urls.home_url,true)
}

const getSummary = (blink) => {
return httpGet(blink, blink.urls.home_url, true);
}

const setArmed = (blink, networkId, armed) => {
let state = armed ? 'arm' : 'disarm';
const url = blink.urls.arm_url + networkId + '/state/' + state;

return httpPost(blink, url, {}, true);
}

const getVideos = (blink, since, page) => {
const url = `${blink.urls.video_url}?since=${since.toISOString()}&page=${page}`
return httpGet(blink, url, true);
}

const imageRefresh = (blink) => {
return httpGet(blink, blink.urls.home_url, true);
}

/* camera API functions */
const snapPicture = (camera) => {
const blink = camera.blink;
const url = camera.image_link;
return httpPost(blink, url, {}, true);
};

const setMotionDetect = (camera, enable) => {
const blink = camera.blink;
const url = camera.arm_link + (enable ? 'enable' : 'disable')
return httpPost(blink, url, {}, true);
}

const statusRefresh = (camera) => {
const blink = camera.blink;
const url = camera.arm_link + 'status';
return httpPost(blink, url, {}, true);
}

const fetchImageData = (camera) => {
const blink = camera.blink;
const thumbnailUrl = camera.thumbnail;
return httpGet(blink, thumbnailUrl, false, true /* binary */);
}

const fetchVideoData = (camera) => {
const blink = camera.blink;
const clipUrl = camera.clip;
return httpGet(blink, clipUrl, false, true /* binary */);
}

const recordClip = (camera) => {
const blink = camera.blink;
const url = camera.arm_link + 'clip';
return httpPost(blink, url, {}, true);
}

/* HTTP helper functions */
const httpGet = (
blink,
url,
json = true,
binary=false
) => {
Logger.debug(`Making GET request to ${url}`);

return blink.auth.get(
url,
blink.auth.getHeaders(),
json,
false, // skipRefreshCheck
binary
);
};

const httpPost = (
blink,
url,
data = null,
json = true
) => {
Logger.debug(`Making POST request to ${url}`);

return blink.auth.post(
url,
blink.auth.getHeaders(),
data,
json
);
};

module.exports = {
requestLogin,
requestTier,
getIDs,
getSummary,
snapPicture,
setMotionDetect,
imageRefresh,
statusRefresh,
fetchImageData,
fetchVideoData,
recordClip,
setArmed,
getVideos
};
Loading