-
Notifications
You must be signed in to change notification settings - Fork 0
/
env-ready.js
203 lines (166 loc) · 5.75 KB
/
env-ready.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
//
// Copyright (c) 2020 Cisco Systems
// Licensed under the MIT License
//
/*
* Code snippet to read variables from ENV
*
* Pre-req:
* - 'environment' macro is installed on the device
*
*/
const xapi = require('xapi');
// Wait for ENV variables to be accesible
xapi.on('env-ready', async (ready) => {
const value = await getenv('DEVICE_SECRET');
xapi.command('UserInterface Message Prompt Display', {
Title: 'ENV',
Text: `$DEVICE_SECRET = ${value}`,
Duration: 10
});
});
//
// ENV library
// - getenv() function
//
// Configure if the communications should be encrypted
const encrypted = false;
const CRYPTO_SECRET = 'secret'; // WARNING: if you modify this secret make sure to also change it in the getenv() function
// Asks the 'Environment' macro to send the value of an environment variable
const ENV_TIMEOUT = 500; // delay for the environment macro to respond
function getenv(variable) {
const decrypt = decipher(CRYPTO_SECRET);
const encrypt = cipher(CRYPTO_SECRET);
return new Promise((resolve, reject) => {
let context = {}
// Wait for response from 'Environment' macro
context.stop = xapi.event.on("Message Send Text", function (msg) {
let parsed;
// Decrypt message
if (encrypted) {
msg = decrypt(msg);
}
try {
parsed = JSON.parse(msg);
}
catch (err) {
console.error(`cannot JSON parse "MessageSent" event: ${msg}: it's ok, simply ignoring this event`);
if (encrypted) {
console.warn('CHECK the Crypto secret match in the "environment" and macros embedding the "getenv" function');
}
return;
}
let data = parsed;
if (data.operation && (data.operation == "get_response")) {
console.debug(`received value: "${data.value}" for env variable: "${data.env}"`);
// Check this is the variable we have requested
if (variable != data.env) {
console.debug(`received incorrect variable, ${data.env} instead of ${variable}, ignoring...`);
return;
}
// If found, stop listening
if (context.stop) {
console.debug(`unsubscribe from "Message Send" events, for variable: ${variable}`);
context.stop();
delete context.stop;
}
resolve(data.value);
return;
}
console.debug(`ignoring "Message Sent" event, not a get_response: ${msg}`);
});
// Send request to get the value for the variable
let data = {
'operation': 'get',
'env': variable
};
let requestMsg = JSON.stringify(data);
// Encrypt
if (encrypted) {
requestMsg = encrypt(requestMsg);
}
xapi.command('Message Send', { Text: requestMsg }).then(() => {
// The Environment macro should respond before TIMEOUT
setTimeout(() => {
if (context.stop) {
console.debug(`unsubscribe from Message send for: ${variable}`);
context.stop();
delete context.stop;
}
let error = new Error('Environment Timeout');
error.code = "TIMEOUT";
return reject(error);
}, ENV_TIMEOUT);
});
});
}
// Introduce a new event 'env-ready' that fires when 'Environment' macro is ready:
// - xapi.on('env-ready')
//
xapi.on('ready', async () => {
const ENV_RETRY_DELAY = 500;
const NB_RETRIES = 4;
let retries = 0;
while (retries < NB_RETRIES) {
if (await checkEnvironmentIsReady()) {
xapi.emit('env-ready', true);
return;
}
else {
// Wait exponentially before retrying
// note: this elapsed time comes on top of the ENV_TIMEOUT for the 'getenv()' function
await timeout(retries * retries * ENV_RETRY_DELAY);
retries++;
}
}
console.debug(`no response from the "Environment" macro after ${NB_RETRIES} tentatives, is it running?`);
xapi.emit('env-ready', false);
});
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function checkEnvironmentIsReady() {
try {
let value = await getenv('PING');
if ('PONG' == value) {
console.debug('PING => PONG: good to proceed...')
console.debug('"Environment" macro is operational');
return true;
}
else {
console.debug('Environment" macro is NOT operational: unexpected value');
return false;
}
}
catch (err) {
if (err.code == 'TIMEOUT') {
console.debug('Environment" macro is NOT operational: timeout');
return false;
}
console.debug('"Environment" macro is NOT operational: unexpected error');
return false;
}
}
// Symetric crypto functions
// Extract from https://stackoverflow.com/questions/18279141/javascript-string-encryption-and-decryption
// Contributed by https://stackoverflow.com/users/2861702/jorgeblom
//
const cipher = salt => {
const textToChars = text => text.split('').map(c => c.charCodeAt(0));
const byteHex = n => ("0" + Number(n).toString(16)).substr(-2);
const applySaltToChar = code => textToChars(salt).reduce((a, b) => a ^ b, code);
return text => text.split('')
.map(textToChars)
.map(applySaltToChar)
.map(byteHex)
.join('');
}
const decipher = salt => {
const textToChars = text => text.split('').map(c => c.charCodeAt(0));
const applySaltToChar = code => textToChars(salt).reduce((a, b) => a ^ b, code);
return encoded => encoded.match(/.{1,2}/g)
.map(hex => parseInt(hex, 16))
.map(applySaltToChar)
.map(charCode => String.fromCharCode(charCode))
.join('');
}