Skip to content

Commit

Permalink
General fixes (#9)
Browse files Browse the repository at this point in the history
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
Show file tree
Hide file tree
Showing 9 changed files with 237 additions and 123 deletions.
35 changes: 34 additions & 1 deletion config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,43 @@
"schema": {
"type": "object",
"properties": {
"auth": {
"title": "Authentication Mode",
"type": "string",
"default": "api-key",
"oneOf": [
{ "title": "API-Key", "enum": ["api-key"] },
{ "title": "Basic Auth", "enum": ["basic"] }
],
"description": "The authentication method GlueHome plugin will use to access your Glue profile and Glue devices. API-Key auth is recommended.",
"required": true
},
"apiKey": {
"title": "apiKey",
"type": "string",
"required": true
"required": false,
"description": "The API-KEY issued using your Glue account.",
"condition": {
"functionBody": "return model.auth === 'api-key';"
}
},
"username": {
"title": "username",
"type": "string",
"required": false,
"description": "You Glue account phone number in international format (e.g. +46734567890)",
"condition": {
"functionBody": "return model.auth === 'basic';"
}
},
"password": {
"title": "password",
"type": "string",
"required": false,
"description": "Your Glue account password",
"condition": {
"functionBody": "return model.auth === 'basic';"
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"displayName": "GlueHome Homebridge Plugin",
"name": "@gluehome/homebridge-gluehome",
"version": "0.1.3",
"version": "0.2.0",
"description": "Homebridge plugin to integrate with GlueHome ecosystem.",
"author": {
"name": "GlueHome"
Expand Down
93 changes: 71 additions & 22 deletions src/api/client.ts
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));
}
}
}
110 changes: 55 additions & 55 deletions src/api/lock.ts
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;
}
Loading

0 comments on commit e8319a5

Please sign in to comment.