From 4435fe1e0ac6b2c90fb1869df1abc5c1f7ad3502 Mon Sep 17 00:00:00 2001 From: Nick Berardi Date: Fri, 24 Feb 2023 12:41:11 -0500 Subject: [PATCH] alexa: ensure we are talking to the correct API endpoint (#580) * alexa plugin: ensure we are talking to the right endpoint region for a customer * added the api endpoint as a visible setting --- plugins/alexa/package.json | 4 +-- plugins/alexa/src/main.ts | 58 +++++++++++++++++++++++++++---- plugins/alexa/src/types/camera.ts | 1 + 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/plugins/alexa/package.json b/plugins/alexa/package.json index 682a01d361..938084322e 100644 --- a/plugins/alexa/package.json +++ b/plugins/alexa/package.json @@ -1,6 +1,6 @@ { "name": "@scrypted/alexa", - "version": "0.0.20", + "version": "0.1.0", "scripts": { "scrypted-setup-project": "scrypted-setup-project", "prescrypted-setup-project": "scrypted-package-json", @@ -35,7 +35,7 @@ "dependencies": { "@types/node": "^16.6.1", "alexa-smarthome-ts": "^0.0.1", - "axios": "^0.24.0", + "axios": "^1.3.4", "uuid": "^9.0.0" }, "devDependencies": { diff --git a/plugins/alexa/src/main.ts b/plugins/alexa/src/main.ts index d259ab934f..28ee2b7119 100644 --- a/plugins/alexa/src/main.ts +++ b/plugins/alexa/src/main.ts @@ -1,5 +1,5 @@ import axios from 'axios'; -import sdk, { HttpRequest, HttpRequestHandler, HttpResponse, MixinProvider, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface } from '@scrypted/sdk'; +import sdk, { HttpRequest, HttpRequestHandler, HttpResponse, MixinProvider, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, Setting, SettingValue, Settings } from '@scrypted/sdk'; import { StorageSettings } from '@scrypted/sdk/storage-settings'; import { AutoenableMixinProvider } from '@scrypted/common/src/autoenable-mixin-provider'; import { isSupported } from './types'; @@ -12,16 +12,22 @@ const { systemManager, deviceManager } = sdk; const client_id = "amzn1.application-oa2-client.3283807e04d8408eb44a698c10f9dd13"; const client_secret = "bed445e2b26730acd818b90e175b275f6b67b18ff8645e571c5b3e311fa75ee9"; -class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, MixinProvider { +class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, MixinProvider, Settings { storageSettings = new StorageSettings(this, { tokenInfo: { hide: true, - json: true, + json: true }, syncedDevices: { multiple: true, - hide: true, + hide: true }, + apiEndpoint: { + title: 'Alexa Endpoint', + description: 'This is the endpoint Alexa will use to send events to. This is set after you login.', + type: 'string', + readonly: true + } }); handlers = new Map(); @@ -85,6 +91,13 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, }); } + getSettings(): Promise { + return this.storageSettings.getSettings(); + } + putSetting(key: string, value: SettingValue): Promise { + return this.storageSettings.putSetting(key, value); + } + async getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any; }): Promise { return mixinDevice; } @@ -99,11 +112,41 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, deviceManager.requestRestart(); } + readonly endpoints: string[] = [ + 'api.amazonalexa.com', + 'api.eu.amazonalexa.com', + 'api.fe.amazonalexa.com' + ]; + + async getAlexaEndpoint() : Promise { + if (this.storageSettings.values.apiEndpoint) + return this.storageSettings.values.apiEndpoint; + + try { + const accessToken = await this.getAccessToken(); + const response = await axios.get(`https://${this.endpoints[0]}/v1/alexaApiEndpoint`, { + headers: { + 'Authorization': 'Bearer ' + accessToken, + } + }); + + const endpoint: string = response.data.endpoints[0]; + this.storageSettings.values.apiEndpoint = endpoint; + return endpoint; + } catch (err) { + this.console.error(err); + + // default to NA/RoW endpoint if we can't get the endpoint. + return this.endpoints[0]; + } + } + async postEvent(data: any) { const accessToken = await this.getAccessToken(); + const endpoint = await this.getAlexaEndpoint(); const self = this; - return axios.post('https://api.amazonalexa.com/v3/events', data, { + return axios.post(`https://${endpoint}/v3/events`, data, { headers: { 'Authorization': 'Bearer ' + accessToken, } @@ -267,6 +310,7 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, const json = JSON.parse(request.body); const { grant } = json.directive.payload; this.storageSettings.values.tokenInfo = grant; + this.storageSettings.values.apiEndpoint = undefined; this.accessToken = undefined; const self = this; @@ -274,6 +318,7 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, self.console.error(`Failed to handle the AcceptGrant directive because ${reason}`); this.storageSettings.values.tokenInfo = undefined; + this.storageSettings.values.apiEndpoint = undefined; this.accessToken = undefined; response.send(JSON.stringify({ @@ -311,6 +356,7 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, this.console.error(`AcceptGrant.Response failed because ${error}`); this.storageSettings.values.tokenInfo = undefined; + this.storageSettings.values.apiEndpoint = undefined; this.accessToken = undefined; throw error; } @@ -455,7 +501,7 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, catch (e) { this.console.error(`request failed due to invalid authorization`, e); response.send(e.message, { - code: 500, + code: 500 }); return; } diff --git a/plugins/alexa/src/types/camera.ts b/plugins/alexa/src/types/camera.ts index 1f9a71d9e3..efad98d103 100644 --- a/plugins/alexa/src/types/camera.ts +++ b/plugins/alexa/src/types/camera.ts @@ -113,6 +113,7 @@ export class AlexaSignalingSession implements RTCSignalingSession { // this could be a low resolution screen, no way of knowing, so never send a // 1080p+ stream. screen: { + devicePixelRatio: 1, // TODO: get this from the device width: 1280, height: 720, }