Skip to content

Commit

Permalink
feat: Added history endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
bflorian committed Aug 11, 2020
1 parent 496ddca commit 0b1e963
Show file tree
Hide file tree
Showing 26 changed files with 15,647 additions and 783 deletions.
2,073 changes: 1,292 additions & 781 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@
"eslint-plugin-eslint-comments": "^3.1.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-jest": "^23.8.2",
"jest": "^25.2.7",
"jest": "^26.1.4",
"node": "^12.13.0",
"prettier": "^2.0.4",
"semantic-release": "^17.1.1",
"ts-jest": "^25.3.1",
"ts-jest": "^26.1.4",
"typedoc": "^0.17.4",
"typescript": "^3.8.3"
},
Expand Down
163 changes: 163 additions & 0 deletions src/endpoint/history.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import {Endpoint} from '../endpoint'
import {EndpointClient, EndpointClientConfig, HttpClientParams} from '../endpoint-client'
import {PaginatedList} from '../pagination'


export interface DeviceActivity {
/** Device ID */
deviceId: string

/** Device nick name */
deviceName: string

/** Location ID */
locationId: string

/** Location name */
locationName: string

/** The IS0-8601 date time strings in UTC of the activity */
time: string

/** Translated human readable string (localized) */
text: string

/** device component ID. Not nullable. */
component: string

/** device component label. Nullable. */
componentLabel?: string

/** capability name */
capability: string

/** attribute name */
attribute: string

/** attribute value */
value: object
unit?: string
data?: Record<string, object>

/** translated attribute name based on 'Accept-Language' requested in header */
translatedAttributeName?: string

/** translated attribute value based on 'Accept-Language' requested in header */
translatedAttributeValue?: string

/** UNIX epoch in milliseconds of the activity time */
epoch: number

/** Hash to differentiate two events with the same epoch value */
hash: number
}

export interface PaginationRequest {
/**
* Paging parameter for going to previous page. Before epoch time (millisecond).
* Return the nearest records (the immediate previous page) with event time before the specified value exclusively. e.g. 1511913638679. Note: type is a long.
*
*/
before?: number

/**
* Paging parameter for going to previous page. Before Hash (long). This needs to be specified when 'before' is specified.
* Please put in associated hash value of the record specified by the 'before' parameter.
*
*/
beforeHash?: number

/**
* Paging parameter for going to next page. After epoch time (millisecond).
* Return the nearest records (the immediate next page) with event time after the specified value exclusively. e.g. 1511913638679. Note: type is a long.
*
*/
after?: number

/**
* Paging parameter for going to next page. After Hash (long). this needs to be specified when 'after' is specified.
* Please put in associated hash value of the record specified by the 'after' parameter.
*
*/
afterHash?: number

/**
* Maximum number of events to return. Defaults to 20
*/
limit?: number
}

export type DeviceHistoryRequest = PaginationRequest & {
locationId?: string | string[]
deviceId?: string | string[]
oldestFirst?: boolean
}

export class HistoryEndpoint extends Endpoint {
constructor(config: EndpointClientConfig) {
super(new EndpointClient('history', config))
}

/**
* Queries for device events. Returns an object that supports explicit paging with next() and previous() as well
* as asynchronous iteration.
*
* Explicit paging:
* ```
* const result = await client.history.devices({deviceId: 'c8fc80fc-6bbb-4b74-a9fa-97acc3d5fa01'})
* for (const item of result.items) {
* console.log(`${item.name} = ${item.value}`)
* }
* while (await next()) {
* for (const item of result.items) {
* console.log(`${item.name} = ${item.value}`)
* }
* }
* ```
*
* Asynchronous iteration
* ```
* for await (const item of client.history.devices({deviceId: 'c8fc80fc-6bbb-4b74-a9fa-97acc3d5fa01'}) {
* console.log(`${item.name} = ${item.value}`)
* }
* ```
*
* @param options query options -- deviceId, limit, before, beforeHash, after, afterHash, oldestFirst, and
* locationId.
*/
public async devices(options: DeviceHistoryRequest = {}): Promise<PaginatedList<DeviceActivity>> {
const params: HttpClientParams = {}
if ('locationId' in options && options.locationId) {
params.locationId = options.locationId
} else if (this.client.config.locationId) {
params.locationId = this.client.config.locationId
} else {
throw new Error('Location ID is undefined')
}
if ('deviceId' in options && options.deviceId) {
params.deviceId = options.deviceId
}
if ('limit' in options && options.limit) {
params.limit = options.limit
}
if ('before' in options && options.before) {
params.pagingBeforeEpoch = options.before
}
if ('beforeHash' in options && options.beforeHash) {
params.pagingBeforeHash = options.beforeHash
}
if ('after' in options && options.after) {
params.pagingAfterEpoch = options.after
}
if ('afterHash' in options && options.afterHash) {
params.pagingAfterHash = options.afterHash
}
if ('oldestFirst' in options) {
params.oldestFirst = `${options.oldestFirst}`
}

return new PaginatedList<DeviceActivity>(
await this.client.get<PaginatedList<DeviceActivity>>('devices', params),
this.client)
}
}
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export * from './signature'
export * from './st-client'
export * from './rest-client'
export * from './types'
export * from './endpoint'
export * from './pagination'

export * from './endpoint/apps'
export * from './endpoint/capabilities'
Expand Down
75 changes: 75 additions & 0 deletions src/pagination.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {EndpointClient} from './endpoint-client'
import {Links} from './types'


export interface PagedResult<T> {
items: T[]
_links?: Links
}

export class PaginatedListIterator<T> implements AsyncIterator<T> {
private index: number

constructor(private client: EndpointClient, private page: PagedResult<T>) {
this.index = 0
}

async next(): Promise<IteratorResult<T>> {
if (this.index < this.page.items.length) {
let done = false
const value = this.page.items[this.index++]
if (this.index === this.page.items.length) {
if (this.page?._links?.next?.href) {
this.index = 0
this.page = await this.client.get<PagedResult<T>>(this.page._links.next.href)
} else {
done = true
}
}
return {done, value}
}
return {done: true, value: undefined}
}
}

export class PaginatedList<T> {
public items: Array<T>

constructor(private page: PagedResult<T>, private client: EndpointClient ) {
this.items = page.items
}

public [Symbol.asyncIterator](): PaginatedListIterator<T> {
return new PaginatedListIterator<T>(this.client, this.page)
}

public hasNext(): boolean {
return !!this.page._links?.next
}

public hasPrevious(): boolean {
return !!this.page._links?.previous
}

public next(): Promise<boolean> {
if (this.page._links?.next?.href) {
return this.client.get<PagedResult<T>>(this.page._links.next.href).then(response => {
this.items = response.items
this.page = response
return !!response._links?.next
})
}
return Promise.reject(new Error('No next results'))
}

public previous(): Promise<boolean> {
if (this.page._links?.previous) {
return this.client.get<PagedResult<T>>(this.page._links.previous.href).then(response => {
this.items = response.items
this.page = response
return !!response._links?.previous
})
}
return Promise.reject(new Error('No previous results'))
}
}
3 changes: 3 additions & 0 deletions src/st-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AppsEndpoint } from './endpoint/apps'
import { CapabilitiesEndpoint } from './endpoint/capabilities'
import { DeviceProfilesEndpoint } from './endpoint/deviceprofiles'
import { DevicesEndpoint } from './endpoint/devices'
import { HistoryEndpoint } from './endpoint/history'
import { InstalledAppsEndpoint } from './endpoint/installedapps'
import { ModesEndpoint } from './endpoint/modes'
import { LocationsEndpoint } from './endpoint/locations'
Expand All @@ -26,6 +27,7 @@ export class SmartThingsClient extends RESTClient {
public readonly capabilities: CapabilitiesEndpoint
public readonly deviceProfiles: DeviceProfilesEndpoint
public readonly devices: DevicesEndpoint
public readonly history: HistoryEndpoint
public readonly installedApps: InstalledAppsEndpoint
public readonly modes: ModesEndpoint
public readonly notifications: NotificationsEndpoint
Expand All @@ -46,6 +48,7 @@ export class SmartThingsClient extends RESTClient {
this.capabilities = new CapabilitiesEndpoint(this.config)
this.deviceProfiles = new DeviceProfilesEndpoint(this.config)
this.devices = new DevicesEndpoint(this.config)
this.history = new HistoryEndpoint(this.config)
this.installedApps = new InstalledAppsEndpoint(this.config)
this.locations = new LocationsEndpoint(this.config)
this.modes = new ModesEndpoint(this.config)
Expand Down
Loading

0 comments on commit 0b1e963

Please sign in to comment.