generated from homebridge/homebridge-plugin-template
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add BasicAuth scheme alternative through configuration. Graceful handling of 503 (lock busy) errors. Increase polling frequency during a lock operation.
- Loading branch information
Stavros Kafouros
authored
Jan 29, 2021
1 parent
257c289
commit e8319a5
Showing
9 changed files
with
237 additions
and
123 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,44 +1,93 @@ | ||
import axios, { AxiosInstance } from "axios" | ||
import { Lock, LockOperation, CreateLockOperation } from "./" | ||
import axios, { AxiosError, AxiosInstance } from 'axios'; | ||
import { Lock, LockOperation, CreateLockOperation } from './'; | ||
import { PLATFORM_NAME, VERSION, OS_VERSION } from '../settings'; | ||
|
||
const API_URL = 'https://user-api.gluehome.com'; | ||
const USER_AGENT = `${PLATFORM_NAME}/${VERSION} (${OS_VERSION})`; | ||
|
||
export async function issueApiKey(username: string, password: string): Promise<string> { | ||
try { | ||
const response = await axios.post(`${API_URL}/v1/api-keys`, { | ||
name: 'homebridge', | ||
scopes: ['locks.write', 'locks.read', 'events.read'], | ||
}, | ||
{ | ||
headers: { | ||
'Contenty-Type': 'application/json', | ||
'User-Agent': USER_AGENT, | ||
}, | ||
auth: { | ||
username: username, | ||
password: password, | ||
}, | ||
}); | ||
|
||
return response.data.apiKey; | ||
} catch(err) { | ||
throw Error(err); | ||
} | ||
} | ||
|
||
export interface ApiError { | ||
code: number; | ||
detail: string; | ||
correlationId: string; | ||
} | ||
|
||
export class GlueApi { | ||
private readonly apiKey: string; | ||
private httpClient: AxiosInstance; | ||
|
||
constructor(apiKey: string) { | ||
this.apiKey = apiKey; | ||
this.apiKey = apiKey; | ||
|
||
this.httpClient = axios.create({ | ||
baseURL: API_URL, | ||
timeout: 60000, | ||
}); | ||
|
||
this.httpClient = axios.create({ | ||
baseURL: "https://user-api.gluehome.com", | ||
timeout: 60000 | ||
}); | ||
this.httpClient.interceptors.request.use(config => { | ||
config.headers.authorization = `Api-Key ${apiKey}`; | ||
config.headers['User-Agent'] = USER_AGENT; | ||
return config; | ||
}, (error: AxiosError) => { | ||
return Promise.reject(error.toJSON()); | ||
}); | ||
|
||
this.httpClient.interceptors.request.use(config => { | ||
config.headers.authorization = `Api-Key ${apiKey}` | ||
return config; | ||
}, (error) => { | ||
return Promise.reject(error); | ||
}); | ||
this.httpClient.interceptors.response.use( | ||
res => res, | ||
err => { | ||
if (err.response === undefined) { | ||
return Promise.reject(err); | ||
} | ||
if (err.response.status === 401) { | ||
return Promise.reject('Wrong authentication data provided. Please check the plugin configuration.'); | ||
} | ||
|
||
const {title, code, correlationId, detail} = err.response.data; | ||
const msg = `${title} (code: ${code} correlationId: ${correlationId} details: ${detail})`; | ||
return Promise.reject(msg); | ||
}, | ||
); | ||
} | ||
|
||
public getLocks(): Promise<Lock[]> { | ||
return this.httpClient.get<Lock[]>("/v1/locks") | ||
.then(res => res.data?.map(Lock.fromJson) ?? []); | ||
return this.httpClient.get<Lock[]>('/v1/locks') | ||
.then(res => res.data?.map(Lock.fromJson) ?? []); | ||
} | ||
|
||
public getLock(id: string): Promise<Lock> { | ||
return this.httpClient.get<Lock>(`/v1/locks/${id}`) | ||
.then(res => Lock.fromJson(res.data)); | ||
return this.httpClient.get<Lock>(`/v1/locks/${id}`) | ||
.then(res => Lock.fromJson(res.data)); | ||
} | ||
|
||
public getLockOperation(id: string, operationId: string): Promise<LockOperation> { | ||
return this.httpClient.get<LockOperation>(`/v1/locks/${id}/operations/${operationId}`) | ||
.then(res => LockOperation.fromJson(res.data)); | ||
return this.httpClient.get<LockOperation>(`/v1/locks/${id}/operations/${operationId}`) | ||
.then(res => LockOperation.fromJson(res.data)); | ||
} | ||
|
||
public createLockOperation(id: string, operation: CreateLockOperation): Promise<LockOperation> { | ||
return this.httpClient.post<LockOperation>(`/v1/locks/${id}/operations`, operation) | ||
.then(res => LockOperation.fromJson(res.data)); | ||
return this.httpClient.post<LockOperation>(`/v1/locks/${id}/operations`, operation) | ||
.then(res => LockOperation.fromJson(res.data)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,89 +1,89 @@ | ||
export class Lock { | ||
constructor( | ||
constructor( | ||
public id: string, | ||
public serialNumber: string, | ||
public description: string, | ||
public firmwareVersion: string, | ||
public batteryStatus: number, | ||
public connectionStatus: LockConnecitionStatus, | ||
public lastLockEvent?: LockEvent) { | ||
} | ||
} | ||
|
||
public getLockModel(): string { | ||
return this.serialNumber.substring(0, 4); | ||
} | ||
public getLockModel(): string { | ||
return this.serialNumber.substring(0, 4); | ||
} | ||
|
||
public isBatteryLow(): boolean { | ||
return this.batteryStatus < 50; | ||
} | ||
public isBatteryLow(): boolean { | ||
return this.batteryStatus < 50; | ||
} | ||
|
||
public static fromJson(json): Lock { | ||
return new Lock( | ||
json.id, | ||
json.serialNumber, | ||
json.description, | ||
json.firmwareVersion, | ||
json.batteryStatus, | ||
json.connectionStatus, | ||
json.lastLockEvent | ||
); | ||
} | ||
public static fromJson(json): Lock { | ||
return new Lock( | ||
json.id, | ||
json.serialNumber, | ||
json.description, | ||
json.firmwareVersion, | ||
json.batteryStatus, | ||
json.connectionStatus, | ||
json.lastLockEvent, | ||
); | ||
} | ||
} | ||
|
||
export enum LockOperationType { | ||
Lock = "lock", | ||
Unlock = "unlock" | ||
}; | ||
Lock = 'lock', | ||
Unlock = 'unlock' | ||
} | ||
|
||
export enum LockOperationStatus { | ||
Pending = "pending", | ||
Completed = "completed", | ||
Timeout = "timeout", | ||
Failed = "failed", | ||
}; | ||
Pending = 'pending', | ||
Completed = 'completed', | ||
Timeout = 'timeout', | ||
Failed = 'failed', | ||
} | ||
|
||
export enum LockConnecitionStatus { | ||
Offline = "offline", | ||
Disconnected = "disconnected", | ||
Connected = "connected", | ||
Busy = "busy", | ||
}; | ||
Offline = 'offline', | ||
Disconnected = 'disconnected', | ||
Connected = 'connected', | ||
Busy = 'busy', | ||
} | ||
|
||
export interface CreateLockOperation { | ||
type: LockOperationType | ||
type: LockOperationType; | ||
} | ||
|
||
export class LockOperation { | ||
constructor( | ||
constructor( | ||
public id: string, | ||
public status: LockOperationStatus, | ||
public reason?: string, | ||
) { } | ||
) { } | ||
|
||
public isFinished(): boolean { | ||
return this.status !== 'pending'; | ||
} | ||
public isFinished(): boolean { | ||
return this.status !== 'pending'; | ||
} | ||
|
||
public static fromJson(json): LockOperation { | ||
return new LockOperation( | ||
json.id, | ||
json.status, | ||
json.reason | ||
) | ||
} | ||
public static fromJson(json): LockOperation { | ||
return new LockOperation( | ||
json.id, | ||
json.status, | ||
json.reason, | ||
); | ||
} | ||
} | ||
|
||
export type EventType = | ||
"unknown" | | ||
"localLock" | | ||
"localUnlock" | | ||
"remoteLock" | | ||
"remoteUnlock" | | ||
"pressAndGo" | | ||
"manualUnlock" | | ||
"manualLock" | ||
'unknown' | | ||
'localLock' | | ||
'localUnlock' | | ||
'remoteLock' | | ||
'remoteUnlock' | | ||
'pressAndGo' | | ||
'manualUnlock' | | ||
'manualLock'; | ||
|
||
interface LockEvent { | ||
eventType: EventType | ||
lastLockEventDate: Date | ||
eventType: EventType; | ||
lastLockEventDate: Date; | ||
} |
Oops, something went wrong.