Skip to content

Commit

Permalink
alexa: ensure we are talking to the correct API endpoint (#580)
Browse files Browse the repository at this point in the history
* alexa plugin: ensure we are talking to the right endpoint region for a customer

* added the api endpoint as a visible setting
  • Loading branch information
nberardi committed Feb 24, 2023
1 parent 214e487 commit 4435fe1
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 8 deletions.
4 changes: 2 additions & 2 deletions plugins/alexa/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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": {
Expand Down
58 changes: 52 additions & 6 deletions plugins/alexa/src/main.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<string, AlexaHandler>();
Expand Down Expand Up @@ -85,6 +91,13 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler,
});
}

getSettings(): Promise<Setting[]> {
return this.storageSettings.getSettings();
}
putSetting(key: string, value: SettingValue): Promise<void> {
return this.storageSettings.putSetting(key, value);
}

async getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any; }): Promise<any> {
return mixinDevice;
}
Expand All @@ -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<string> {
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,
}
Expand Down Expand Up @@ -267,13 +310,15 @@ 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;
let accessToken = await this.getAccessToken().catch(reason => {
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({
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions plugins/alexa/src/types/camera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down

0 comments on commit 4435fe1

Please sign in to comment.