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
102 changes: 66 additions & 36 deletions packages/ring-client-api/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { RefreshTokenAuth, SessionOptions } from './rest-client.ts'
import { clientApi, deviceApi, RingRestClient } from './rest-client.ts'
import {
clientApi,
deviceInfoApi,
locationInfoApi,
RingRestClient,
} from './rest-client.ts'
import { Location } from './location.ts'
import type {
BaseStation,
Expand All @@ -14,7 +19,12 @@ import type {
UnknownDevice,
UserLocation,
} from './ring-types.ts'
import { PushNotificationAction, RingDeviceType } from './ring-types.ts'
import {
ChimeModel,
PushNotificationAction,
RingCameraKind,
RingDeviceType,
} from './ring-types.ts'
import type { AnyCameraData } from './ring-camera.ts'
import { RingCamera } from './ring-camera.ts'
import { RingChime } from './ring-chime.ts'
Expand All @@ -38,6 +48,17 @@ import { PushReceiver } from '@eneris/push-receiver'
import { RingIntercom } from './ring-intercom.ts'
import JSONbig from 'json-bigint'

const doorbellKinds: Set<string> = new Set(
Object.values(RingCameraKind).filter(
(k) =>
k.startsWith('doorbot') ||
k.startsWith('doorbell') ||
k.startsWith('lpd_') ||
k.startsWith('jbox_') ||
k.startsWith('cocoa_doorbell'),
),
)
Comment on lines +51 to +60

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be cleaner to encapsulate in an exported predicate in ring-types.ts, similar to isWebSocketSupportedAsset that's already implemented there. If placed in proximity to the RingCameraKind type, it would also improve maintenance discoverability. E.g.,

export type RingCameraKind = ...

export function isDoorbellKind({ kind }: { kind: RingCameraKind }) {
    return kind && (kind.startsWith( ... ) || ... );
}

Or more tersely with a bound regex .test():

export const isDoorbellKind =
RegExp.prototype.test.bind(/^(?:(cocoa_)?doorb(ell|ot)|lpd_|jbox_)/) as
(kind: RingCameraKind) => boolean


export interface RingApiOptions extends SessionOptions {
locationIds?: string[]
cameraStatusPollingSeconds?: number
Expand Down Expand Up @@ -82,50 +103,59 @@ export class RingApi extends Subscribed {
}

async fetchRingDevices() {
const {
doorbots,
chimes,
authorized_doorbots: authorizedDoorbots,
stickup_cams: stickupCams,
base_stations: baseStations,
beams_bridges: beamBridges,
other: otherDevices,
} = await this.restClient.request<{
doorbots: CameraData[]
chimes: ChimeData[]
authorized_doorbots: CameraData[]
stickup_cams: CameraData[]
base_stations: BaseStation[]
beams_bridges: BeamBridge[]
other: (
const { devices } = await this.restClient.request<{
devices: (
| CameraData
| ChimeData
| BaseStation
| BeamBridge
| IntercomHandsetData
| OnvifCameraData
| ThirdPartyGarageDoorOpener
| UnknownDevice
)[]
}>({ url: clientApi('ring_devices') }),
}>({ url: deviceInfoApi('devices') }),
doorbots = [] as CameraData[],
authorizedDoorbots = [] as CameraData[],
stickupCams = [] as CameraData[],
chimes = [] as ChimeData[],
baseStations = [] as BaseStation[],
beamBridges = [] as BeamBridge[],
onvifCameras = [] as OnvifCameraData[],
intercoms = [] as IntercomHandsetData[],
thirdPartyGarageDoorOpeners = [] as ThirdPartyGarageDoorOpener[],
unknownDevices = [] as UnknownDevice[]

otherDevices.forEach((device) => {
switch (device.kind) {
case RingDeviceType.OnvifCamera:
onvifCameras.push(device as OnvifCameraData)
break
case RingDeviceType.IntercomHandsetVideo:
case RingDeviceType.IntercomHandsetAudio:
intercoms.push(device as IntercomHandsetData)
break
case RingDeviceType.ThirdPartyGarageDoorOpener:
thirdPartyGarageDoorOpeners.push(device as ThirdPartyGarageDoorOpener)
break
default:
unknownDevices.push(device)
break
for (const device of devices) {
const kind = device.kind as string

if (doorbellKinds.has(kind)) {
Comment on lines +129 to +132
if ((device as CameraData).owned === false) {
authorizedDoorbots.push(device as CameraData)
} else {
doorbots.push(device as CameraData)
}
} else if (kind in ChimeModel) {
chimes.push(device as ChimeData)
} else if (kind.startsWith('base_station')) {
baseStations.push(device as BaseStation)
} else if (kind.startsWith('beams_bridge')) {
beamBridges.push(device as BeamBridge)
} else if (kind === RingDeviceType.OnvifCamera) {
onvifCameras.push(device as OnvifCameraData)
} else if (
kind === RingDeviceType.IntercomHandsetAudio ||
kind === RingDeviceType.IntercomHandsetVideo
) {
intercoms.push(device as IntercomHandsetData)
} else if (kind === RingDeviceType.ThirdPartyGarageDoorOpener) {
thirdPartyGarageDoorOpeners.push(device as ThirdPartyGarageDoorOpener)
} else if (kind in RingCameraKind) {
stickupCams.push(device as CameraData)
} else {
unknownDevices.push(device as UnknownDevice)
}
})
}

return {
doorbots,
Expand Down Expand Up @@ -397,7 +427,7 @@ export class RingApi extends Subscribed {
async fetchRawLocations() {
const { user_locations: rawLocations } = await this.restClient.request<{
user_locations: UserLocation[]
}>({ url: deviceApi('locations') })
}>({ url: locationInfoApi('locations') })

if (!rawLocations) {
throw new Error(
Expand Down
10 changes: 10 additions & 0 deletions packages/ring-client-api/rest-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ const fetchAgent = new Agent({
deviceApiBaseUrl = 'https://api.ring.com/devices/v1/',
commandsApiBaseUrl = 'https://api.ring.com/commands/v1/',
appApiBaseUrl = 'https://prd-api-us.prd.rings.solutions/api/v1/',
deviceInfoApiBaseUrl = 'https://api.ring.com/device_info/v3/',
locationInfoApiBaseUrl = 'https://api.ring.com/location_info/v3/',
apiVersion = 11

export function clientApi(path: string) {
Expand All @@ -64,6 +66,14 @@ export function appApi(path: string) {
return appApiBaseUrl + path
}

export function deviceInfoApi(path: string) {
return deviceInfoApiBaseUrl + path
}

export function locationInfoApi(path: string) {
return locationInfoApiBaseUrl + path
}

export interface ExtendedResponse {
responseTimestamp: number
timeMillis: number
Expand Down
10 changes: 9 additions & 1 deletion packages/ring-client-api/ring-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -429,13 +429,21 @@ export interface LocationAddress {
export interface UserLocation {
address: LocationAddress
created_at: string
geo_coordinates: { latitude: string; longitude: string }
geo_coordinates: { latitude: string | number; longitude: string | number }
geo_service_verified: 'address_only' | string
location_id: string
name: string
owner_id: number
updated_at: string
user_verified: boolean
is_owner?: boolean
operation_set?: string
entitlements?: {
business_command_center?: boolean
business_role_management?: boolean
vm_virtual_security_guard?: boolean
}
Comment on lines +440 to +445

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious - why are these required for the broader change? Could the type spec be generalized to allow flexible parsing of future changes (e.g., key-value map)?

location_resource_id?: string
}

export interface TicketAsset {
Expand Down
Loading