@alwaysai/device-agent 0.0.12 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/application-control/backup.d.ts.map +1 -1
- package/lib/application-control/backup.js +11 -5
- package/lib/application-control/backup.js.map +1 -1
- package/lib/application-control/config.d.ts +12 -4
- package/lib/application-control/config.d.ts.map +1 -1
- package/lib/application-control/config.js +59 -16
- package/lib/application-control/config.js.map +1 -1
- package/lib/application-control/environment-variables.d.ts.map +1 -1
- package/lib/application-control/environment-variables.js.map +1 -1
- package/lib/application-control/index.d.ts +5 -5
- package/lib/application-control/index.d.ts.map +1 -1
- package/lib/application-control/index.js +4 -6
- package/lib/application-control/index.js.map +1 -1
- package/lib/application-control/install.d.ts +1 -1
- package/lib/application-control/install.d.ts.map +1 -1
- package/lib/application-control/install.js +58 -57
- package/lib/application-control/install.js.map +1 -1
- package/lib/application-control/models.d.ts +7 -5
- package/lib/application-control/models.d.ts.map +1 -1
- package/lib/application-control/models.js +78 -57
- package/lib/application-control/models.js.map +1 -1
- package/lib/application-control/status.d.ts +0 -6
- package/lib/application-control/status.d.ts.map +1 -1
- package/lib/application-control/status.js +21 -33
- package/lib/application-control/status.js.map +1 -1
- package/lib/application-control/utils.d.ts +3 -2
- package/lib/application-control/utils.d.ts.map +1 -1
- package/lib/application-control/utils.js +54 -34
- package/lib/application-control/utils.js.map +1 -1
- package/lib/cloud-connection/app-install-status.d.ts +16 -0
- package/lib/cloud-connection/app-install-status.d.ts.map +1 -0
- package/lib/cloud-connection/app-install-status.js +53 -0
- package/lib/cloud-connection/app-install-status.js.map +1 -0
- package/lib/cloud-connection/bootstrap-provision.d.ts +2 -0
- package/lib/cloud-connection/bootstrap-provision.d.ts.map +1 -0
- package/lib/cloud-connection/bootstrap-provision.js +34 -0
- package/lib/cloud-connection/bootstrap-provision.js.map +1 -0
- package/lib/cloud-connection/cmd-status.d.ts +16 -0
- package/lib/cloud-connection/cmd-status.d.ts.map +1 -0
- package/lib/cloud-connection/cmd-status.js +49 -0
- package/lib/cloud-connection/cmd-status.js.map +1 -0
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +21 -34
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +211 -387
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/device-agent.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent.js +22 -26
- package/lib/cloud-connection/device-agent.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.d.ts +34 -0
- package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -0
- package/lib/cloud-connection/live-updates-handler.js +167 -0
- package/lib/cloud-connection/live-updates-handler.js.map +1 -0
- package/lib/cloud-connection/messages.d.ts +14 -0
- package/lib/cloud-connection/messages.d.ts.map +1 -0
- package/lib/cloud-connection/messages.js +38 -0
- package/lib/cloud-connection/messages.js.map +1 -0
- package/lib/cloud-connection/passthrough-handler.d.ts +11 -0
- package/lib/cloud-connection/passthrough-handler.d.ts.map +1 -0
- package/lib/cloud-connection/passthrough-handler.js +59 -0
- package/lib/cloud-connection/passthrough-handler.js.map +1 -0
- package/lib/cloud-connection/publisher.d.ts +15 -0
- package/lib/cloud-connection/publisher.d.ts.map +1 -0
- package/lib/cloud-connection/publisher.js +58 -0
- package/lib/cloud-connection/publisher.js.map +1 -0
- package/lib/cloud-connection/shadow-handler.d.ts +33 -0
- package/lib/cloud-connection/shadow-handler.d.ts.map +1 -0
- package/lib/cloud-connection/shadow-handler.js +108 -0
- package/lib/cloud-connection/shadow-handler.js.map +1 -0
- package/lib/cloud-connection/shadow-handler.test.d.ts +2 -0
- package/lib/cloud-connection/shadow-handler.test.d.ts.map +1 -0
- package/lib/cloud-connection/shadow-handler.test.js +321 -0
- package/lib/cloud-connection/shadow-handler.test.js.map +1 -0
- package/lib/cloud-connection/shadow.d.ts +16 -0
- package/lib/cloud-connection/shadow.d.ts.map +1 -0
- package/lib/cloud-connection/shadow.js +36 -0
- package/lib/cloud-connection/shadow.js.map +1 -0
- package/lib/device-control/device-control.d.ts.map +1 -1
- package/lib/device-control/device-control.js +1 -0
- package/lib/device-control/device-control.js.map +1 -1
- package/lib/docker/docker-cmd.js +1 -1
- package/lib/docker/docker-compose-cmd.d.ts.map +1 -1
- package/lib/docker/docker-compose-cmd.js +1 -1
- package/lib/docker/docker-compose-cmd.js.map +1 -1
- package/lib/endpoints.js +10 -10
- package/lib/endpoints.js.map +1 -1
- package/lib/environment.d.ts +1 -0
- package/lib/environment.d.ts.map +1 -1
- package/lib/environment.js +2 -1
- package/lib/environment.js.map +1 -1
- package/lib/infrastructure/agent-config.d.ts +15 -58
- package/lib/infrastructure/agent-config.d.ts.map +1 -1
- package/lib/infrastructure/agent-config.js +22 -15
- package/lib/infrastructure/agent-config.js.map +1 -1
- package/lib/infrastructure/agent-config.test.js +25 -23
- package/lib/infrastructure/agent-config.test.js.map +1 -1
- package/lib/infrastructure/system-id.d.ts +2 -0
- package/lib/infrastructure/system-id.d.ts.map +1 -0
- package/lib/infrastructure/system-id.js +21 -0
- package/lib/infrastructure/system-id.js.map +1 -0
- package/lib/infrastructure/tokens-and-device-cfg.d.ts +4 -0
- package/lib/infrastructure/tokens-and-device-cfg.d.ts.map +1 -0
- package/lib/infrastructure/tokens-and-device-cfg.js +27 -0
- package/lib/infrastructure/tokens-and-device-cfg.js.map +1 -0
- package/lib/infrastructure/urls.d.ts.map +1 -1
- package/lib/infrastructure/urls.js +3 -3
- package/lib/infrastructure/urls.js.map +1 -1
- package/lib/local-connection/rabbitmq-connection.d.ts +4 -0
- package/lib/local-connection/rabbitmq-connection.d.ts.map +1 -0
- package/lib/local-connection/rabbitmq-connection.js +58 -0
- package/lib/local-connection/rabbitmq-connection.js.map +1 -0
- package/lib/root.d.ts.map +1 -1
- package/lib/root.js +2 -7
- package/lib/root.js.map +1 -1
- package/lib/subcommands/app/app.d.ts +2 -1
- package/lib/subcommands/app/app.d.ts.map +1 -1
- package/lib/subcommands/app/app.js +112 -77
- package/lib/subcommands/app/app.js.map +1 -1
- package/lib/subcommands/app/index.js +2 -2
- package/lib/subcommands/device/clean.d.ts +2 -0
- package/lib/subcommands/device/clean.d.ts.map +1 -0
- package/lib/subcommands/device/clean.js +29 -0
- package/lib/subcommands/device/clean.js.map +1 -0
- package/lib/subcommands/device/device.d.ts +1 -1
- package/lib/subcommands/device/device.d.ts.map +1 -1
- package/lib/subcommands/device/device.js +44 -33
- package/lib/subcommands/device/device.js.map +1 -1
- package/lib/subcommands/device/index.d.ts.map +1 -1
- package/lib/subcommands/device/index.js +2 -1
- package/lib/subcommands/device/index.js.map +1 -1
- package/lib/subcommands/get-model-package.js +5 -5
- package/lib/subcommands/index.d.ts +0 -1
- package/lib/subcommands/index.d.ts.map +1 -1
- package/lib/subcommands/index.js +1 -1
- package/lib/subcommands/login.d.ts +0 -1
- package/lib/subcommands/login.d.ts.map +1 -1
- package/lib/subcommands/login.js +6 -14
- package/lib/subcommands/login.js.map +1 -1
- package/lib/util/clean-certs.d.ts +2 -0
- package/lib/util/clean-certs.d.ts.map +1 -0
- package/lib/util/clean-certs.js +16 -0
- package/lib/util/clean-certs.js.map +1 -0
- package/lib/util/directories.d.ts +16 -15
- package/lib/util/directories.d.ts.map +1 -1
- package/lib/util/directories.js +45 -26
- package/lib/util/directories.js.map +1 -1
- package/lib/util/fetch-with-timeout.d.ts +4 -0
- package/lib/util/fetch-with-timeout.d.ts.map +1 -0
- package/lib/util/fetch-with-timeout.js +15 -0
- package/lib/util/fetch-with-timeout.js.map +1 -0
- package/lib/util/get-device-id.d.ts +1 -1
- package/lib/util/get-device-id.d.ts.map +1 -1
- package/lib/util/get-device-id.js +14 -19
- package/lib/util/get-device-id.js.map +1 -1
- package/lib/util/http-client.d.ts +1 -1
- package/lib/util/http-client.d.ts.map +1 -1
- package/lib/util/http-client.js +10 -8
- package/lib/util/http-client.js.map +1 -1
- package/lib/util/logger.d.ts.map +1 -1
- package/lib/util/logger.js +4 -5
- package/lib/util/logger.js.map +1 -1
- package/lib/util/require-logged-in-and-paid-plan.d.ts +2 -0
- package/lib/util/require-logged-in-and-paid-plan.d.ts.map +1 -0
- package/lib/util/require-logged-in-and-paid-plan.js +18 -0
- package/lib/util/require-logged-in-and-paid-plan.js.map +1 -0
- package/lib/util/run-in-dir.d.ts.map +1 -1
- package/lib/util/run-in-dir.js +1 -0
- package/lib/util/run-in-dir.js.map +1 -1
- package/lib/util/timer.d.ts +2 -0
- package/lib/util/timer.d.ts.map +1 -0
- package/lib/util/timer.js +6 -0
- package/lib/util/timer.js.map +1 -0
- package/package.json +32 -35
- package/readme.md +100 -89
- package/src/application-control/backup.ts +11 -6
- package/src/application-control/config.ts +75 -13
- package/src/application-control/environment-variables.ts +3 -3
- package/src/application-control/index.ts +18 -11
- package/src/application-control/install.ts +82 -78
- package/src/application-control/models.ts +104 -72
- package/src/application-control/status.ts +29 -40
- package/src/application-control/utils.ts +66 -38
- package/src/cloud-connection/app-install-status.ts +62 -0
- package/src/cloud-connection/bootstrap-provision.ts +40 -0
- package/src/cloud-connection/cmd-status.ts +52 -0
- package/src/cloud-connection/device-agent-cloud-connection.ts +302 -526
- package/src/cloud-connection/device-agent.ts +31 -38
- package/src/cloud-connection/live-updates-handler.ts +226 -0
- package/src/cloud-connection/messages.ts +39 -0
- package/src/cloud-connection/passthrough-handler.ts +67 -0
- package/src/cloud-connection/publisher.ts +86 -0
- package/src/cloud-connection/shadow-handler.test.ts +361 -0
- package/src/cloud-connection/shadow-handler.ts +175 -0
- package/src/cloud-connection/shadow.ts +50 -0
- package/src/device-control/device-control.ts +1 -0
- package/src/docker/docker-cmd.ts +1 -1
- package/src/docker/docker-compose-cmd.ts +5 -2
- package/src/endpoints.ts +9 -9
- package/src/environment.ts +11 -3
- package/src/infrastructure/agent-config.test.ts +33 -29
- package/src/infrastructure/agent-config.ts +57 -22
- package/src/infrastructure/system-id.ts +18 -0
- package/src/infrastructure/tokens-and-device-cfg.ts +34 -0
- package/src/infrastructure/urls.ts +4 -2
- package/src/local-connection/rabbitmq-connection.ts +53 -0
- package/src/root.ts +2 -8
- package/src/subcommands/app/app.ts +119 -83
- package/src/subcommands/app/index.ts +3 -3
- package/src/subcommands/device/clean.ts +26 -0
- package/src/subcommands/device/device.ts +67 -54
- package/src/subcommands/device/index.ts +2 -1
- package/src/subcommands/get-model-package.ts +5 -5
- package/src/subcommands/index.ts +1 -1
- package/src/subcommands/login.ts +6 -14
- package/src/util/clean-certs.ts +12 -0
- package/src/util/directories.ts +68 -52
- package/src/util/fetch-with-timeout.ts +18 -0
- package/src/util/get-device-id.ts +16 -18
- package/src/util/http-client.ts +18 -13
- package/src/util/logger.ts +6 -6
- package/src/util/require-logged-in-and-paid-plan.ts +16 -0
- package/src/util/run-in-dir.ts +2 -1
- package/src/util/timer.ts +1 -0
- package/lib/infrastructure/certificates-and-tokens.d.ts +0 -6
- package/lib/infrastructure/certificates-and-tokens.d.ts.map +0 -1
- package/lib/infrastructure/certificates-and-tokens.js +0 -43
- package/lib/infrastructure/certificates-and-tokens.js.map +0 -1
- package/src/infrastructure/certificates-and-tokens.ts +0 -53
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import { ModelInstallPayload } from '@alwaysai/device-agent-schemas';
|
|
3
3
|
import {
|
|
4
4
|
appModelsAddComponent,
|
|
5
5
|
appModelsRemoveAllComponent,
|
|
6
6
|
appModelsRemoveComponent,
|
|
7
|
-
appModelsUpdateComponent
|
|
7
|
+
appModelsUpdateComponent
|
|
8
8
|
} from 'alwaysai/lib/components/app';
|
|
9
9
|
import { JsSpawner } from 'alwaysai/lib/util';
|
|
10
10
|
import { logger } from '../util/logger';
|
|
@@ -13,15 +13,19 @@ import { join, dirname } from 'path';
|
|
|
13
13
|
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
14
14
|
import { copyDir } from '../util/copy-dir';
|
|
15
15
|
import { runInDir } from '../util/run-in-dir';
|
|
16
|
-
import {
|
|
16
|
+
import { restartApp } from './status';
|
|
17
17
|
|
|
18
18
|
import { ModelDetails } from './types';
|
|
19
19
|
import {
|
|
20
20
|
buildApp,
|
|
21
21
|
downloadPackageUsingPresignedUrl,
|
|
22
22
|
getAppDir,
|
|
23
|
-
|
|
23
|
+
requireAppReady
|
|
24
24
|
} from './utils';
|
|
25
|
+
import { MODEL_JSON_FILE_NAME } from 'alwaysai/lib/core/model';
|
|
26
|
+
import { APP_MODELS_DIRECTORY_NAME } from 'alwaysai/lib/constants';
|
|
27
|
+
import { readAppCfgFile, writeAppCfgFile } from './config';
|
|
28
|
+
import { AppConfig } from '@alwaysai/app-configuration-schemas';
|
|
25
29
|
|
|
26
30
|
export async function getAppModels(props: { projectId: string }) {
|
|
27
31
|
const { projectId } = props;
|
|
@@ -45,23 +49,12 @@ export async function getAppModels(props: { projectId: string }) {
|
|
|
45
49
|
return modelDetails;
|
|
46
50
|
}
|
|
47
51
|
|
|
48
|
-
export async function
|
|
52
|
+
export async function removeModel(props: {
|
|
53
|
+
projectId: string;
|
|
54
|
+
modelId: string;
|
|
55
|
+
}) {
|
|
49
56
|
const { projectId, modelId } = props;
|
|
50
|
-
await
|
|
51
|
-
|
|
52
|
-
const appDir = getAppDir(projectId);
|
|
53
|
-
await appModelsAddComponent({
|
|
54
|
-
yes: false,
|
|
55
|
-
dir: appDir,
|
|
56
|
-
id: modelId,
|
|
57
|
-
addToProject: false,
|
|
58
|
-
});
|
|
59
|
-
await buildApp({ appDir });
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export async function removeModel(props: { projectId: string; modelId: string }) {
|
|
63
|
-
const { projectId, modelId } = props;
|
|
64
|
-
await requireAppInstalled({ projectId });
|
|
57
|
+
await requireAppReady({ projectId });
|
|
65
58
|
|
|
66
59
|
const appDir = getAppDir(projectId);
|
|
67
60
|
|
|
@@ -71,16 +64,19 @@ export async function removeModel(props: { projectId: string; modelId: string })
|
|
|
71
64
|
{
|
|
72
65
|
id: modelId,
|
|
73
66
|
purge: true,
|
|
74
|
-
removeFromProject: false
|
|
75
|
-
}
|
|
67
|
+
removeFromProject: false
|
|
68
|
+
}
|
|
76
69
|
],
|
|
77
|
-
appDir
|
|
70
|
+
appDir
|
|
78
71
|
);
|
|
79
72
|
}
|
|
80
73
|
|
|
81
|
-
export async function replaceModels(props: {
|
|
74
|
+
export async function replaceModels(props: {
|
|
75
|
+
projectId: string;
|
|
76
|
+
modelIds: string[];
|
|
77
|
+
}) {
|
|
82
78
|
const { projectId, modelIds } = props;
|
|
83
|
-
await
|
|
79
|
+
await requireAppReady({ projectId });
|
|
84
80
|
|
|
85
81
|
const appDir = getAppDir(projectId);
|
|
86
82
|
|
|
@@ -89,17 +85,17 @@ export async function replaceModels(props: { projectId: string; modelIds: string
|
|
|
89
85
|
[
|
|
90
86
|
{
|
|
91
87
|
purge: true,
|
|
92
|
-
removeFromProject: false
|
|
93
|
-
}
|
|
88
|
+
removeFromProject: false
|
|
89
|
+
}
|
|
94
90
|
],
|
|
95
|
-
appDir
|
|
91
|
+
appDir
|
|
96
92
|
);
|
|
97
93
|
for (const modelId of modelIds) {
|
|
98
94
|
await appModelsAddComponent({
|
|
99
95
|
yes: false,
|
|
100
96
|
dir: appDir,
|
|
101
97
|
id: modelId,
|
|
102
|
-
addToProject: false
|
|
98
|
+
addToProject: false
|
|
103
99
|
});
|
|
104
100
|
}
|
|
105
101
|
await buildApp({ appDir });
|
|
@@ -107,88 +103,124 @@ export async function replaceModels(props: { projectId: string; modelIds: string
|
|
|
107
103
|
|
|
108
104
|
export async function updateModels(props: { projectId: string }) {
|
|
109
105
|
const { projectId } = props;
|
|
110
|
-
await
|
|
106
|
+
await requireAppReady({ projectId });
|
|
111
107
|
|
|
112
108
|
const appDir = getAppDir(projectId);
|
|
113
109
|
await appModelsUpdateComponent({
|
|
114
110
|
yes: false,
|
|
115
|
-
dir: appDir
|
|
111
|
+
dir: appDir
|
|
116
112
|
});
|
|
117
113
|
await buildApp({ appDir });
|
|
118
114
|
}
|
|
119
115
|
|
|
120
116
|
export async function installModelsWithPresignedURLs(
|
|
121
117
|
modelPayloads: ModelInstallPayload[],
|
|
122
|
-
targetDir: string
|
|
118
|
+
targetDir: string
|
|
123
119
|
) {
|
|
124
120
|
const spawner = JsSpawner();
|
|
121
|
+
await spawner.mkdirp(targetDir);
|
|
125
122
|
await Promise.all(
|
|
126
123
|
modelPayloads.map(async (payload: ModelInstallPayload) => {
|
|
124
|
+
logger.info(`Installing ${payload.id}: ${payload.version}`);
|
|
125
|
+
const version = payload.version;
|
|
127
126
|
const modelDest = `${targetDir}/${payload.id}`;
|
|
128
127
|
await spawner.mkdirp(modelDest);
|
|
129
128
|
const localDest = `${modelDest}/${payload.version}.tar.gz`;
|
|
130
129
|
await downloadPackageUsingPresignedUrl({
|
|
131
130
|
localDest,
|
|
132
|
-
presignedUrl: payload.modelSignedUrl
|
|
131
|
+
presignedUrl: payload.modelSignedUrl
|
|
133
132
|
});
|
|
134
133
|
await spawner.untar(createReadStream(localDest), dirname(modelDest));
|
|
134
|
+
await updateModelJson(modelDest, (modelJson) => ({
|
|
135
|
+
...modelJson,
|
|
136
|
+
version
|
|
137
|
+
}));
|
|
135
138
|
await spawner.rimraf(localDest);
|
|
136
|
-
})
|
|
139
|
+
})
|
|
137
140
|
);
|
|
141
|
+
|
|
142
|
+
async function readModelJson(dir: string) {
|
|
143
|
+
const filePath = spawner.resolvePath(dir, MODEL_JSON_FILE_NAME);
|
|
144
|
+
const output = await spawner.readFile(filePath);
|
|
145
|
+
const parsed = JSON.parse(output);
|
|
146
|
+
return parsed;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function updateModelJson(dir: string, updater: (current: any) => any) {
|
|
150
|
+
const parsed = await readModelJson(dir);
|
|
151
|
+
const updated = updater(parsed);
|
|
152
|
+
const filePath = spawner.resolvePath(dir, MODEL_JSON_FILE_NAME);
|
|
153
|
+
const serialized = JSON.stringify(updated, null, 2);
|
|
154
|
+
await spawner.writeFile(filePath, serialized);
|
|
155
|
+
}
|
|
138
156
|
}
|
|
139
157
|
|
|
140
|
-
export async function updateModelsWithPresignedUrls(
|
|
141
|
-
|
|
142
|
-
modelInstallPayloads: ModelInstallPayload[]
|
|
143
|
-
|
|
144
|
-
|
|
158
|
+
export async function updateModelsWithPresignedUrls(props: {
|
|
159
|
+
projectId: string;
|
|
160
|
+
modelInstallPayloads: ModelInstallPayload[];
|
|
161
|
+
appReleaseHash: string;
|
|
162
|
+
newAppCfg: AppConfig;
|
|
163
|
+
}) {
|
|
164
|
+
const { projectId, modelInstallPayloads, appReleaseHash, newAppCfg } = props;
|
|
165
|
+
logger.info(`Installing models for ${projectId}`);
|
|
145
166
|
const spawner = JsSpawner();
|
|
146
|
-
const appDir = getAppDir(
|
|
147
|
-
|
|
148
|
-
|
|
167
|
+
const appDir = getAppDir(projectId);
|
|
168
|
+
|
|
169
|
+
if (await AgentConfigFile().isAppPresent({ projectId })) {
|
|
170
|
+
if (!(await AgentConfigFile().isAppReady({ projectId }))) {
|
|
171
|
+
throw new Error('Application already has installation in progress!');
|
|
172
|
+
}
|
|
173
|
+
logger.info('Updating installed application');
|
|
174
|
+
await AgentConfigFile().setAppInstalling({
|
|
175
|
+
projectId,
|
|
176
|
+
version: appReleaseHash
|
|
177
|
+
});
|
|
178
|
+
} else {
|
|
179
|
+
throw new Error('Application is not installed!');
|
|
180
|
+
}
|
|
181
|
+
const ogAppCfg = await readAppCfgFile({ projectId });
|
|
182
|
+
|
|
183
|
+
const ogDir = path.join(appDir, APP_MODELS_DIRECTORY_NAME);
|
|
184
|
+
// Copy all current models to restore dir in case of failure
|
|
149
185
|
const restoreDir = `${ogDir}.restore`;
|
|
186
|
+
await spawner.rimraf(restoreDir);
|
|
150
187
|
await copyDir({ srcPath: ogDir, destPath: restoreDir });
|
|
151
|
-
await spawner.rimraf(tmpDir);
|
|
152
188
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const existingModels = await getAppModels({ projectId: project });
|
|
156
|
-
for (const model of existingModels) {
|
|
157
|
-
if (
|
|
158
|
-
!modelInstallPayloads
|
|
159
|
-
.map((newModel: ModelInstallPayload) => newModel.id)
|
|
160
|
-
.includes(model.modelId)
|
|
161
|
-
) {
|
|
162
|
-
// model does not need to be updated
|
|
163
|
-
await copyDir({
|
|
164
|
-
srcPath: `${ogDir}/${model.modelId}`,
|
|
165
|
-
destPath: `${tmpDir}/${model.modelId}`,
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
}
|
|
189
|
+
// Create temp dir to install new models
|
|
190
|
+
const tmpDir = `${ogDir}.tmp`;
|
|
169
191
|
|
|
170
|
-
|
|
192
|
+
try {
|
|
193
|
+
await spawner.rimraf(tmpDir);
|
|
194
|
+
await copyDir({ srcPath: ogDir, destPath: tmpDir });
|
|
171
195
|
await installModelsWithPresignedURLs(modelInstallPayloads, tmpDir);
|
|
196
|
+
// TODO: Purge outdated models
|
|
172
197
|
await spawner.rimraf(ogDir);
|
|
173
198
|
await copyDir({ srcPath: tmpDir, destPath: ogDir });
|
|
174
199
|
await spawner.rimraf(tmpDir);
|
|
175
200
|
|
|
176
|
-
await
|
|
201
|
+
await writeAppCfgFile({ projectId, appCfg: newAppCfg });
|
|
202
|
+
await buildApp({ appDir });
|
|
177
203
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
204
|
+
await AgentConfigFile().setAppInstalled({
|
|
205
|
+
projectId,
|
|
206
|
+
version: appReleaseHash
|
|
207
|
+
});
|
|
182
208
|
|
|
183
|
-
|
|
184
|
-
restartApp({ projectId: project });
|
|
185
|
-
}
|
|
209
|
+
await restartApp({ projectId });
|
|
186
210
|
|
|
187
|
-
logger.info(`Models installed for project ${
|
|
211
|
+
logger.info(`Models installed for project ${projectId}`);
|
|
212
|
+
/* Leave error handling to higher level so errors are sent to cloud
|
|
188
213
|
} catch (e) {
|
|
189
|
-
logger.error(
|
|
190
|
-
|
|
214
|
+
logger.error(
|
|
215
|
+
'Error updating app models from presigned URL, restoring models.',
|
|
216
|
+
e
|
|
217
|
+
);
|
|
191
218
|
await spawner.rimraf(ogDir);
|
|
192
219
|
await copyDir({ srcPath: restoreDir, destPath: ogDir });
|
|
220
|
+
await writeAppCfgFile({ projectId, appCfg: ogAppCfg });
|
|
221
|
+
*/
|
|
222
|
+
} finally {
|
|
223
|
+
await spawner.rimraf(tmpDir);
|
|
224
|
+
await spawner.rimraf(restoreDir);
|
|
193
225
|
}
|
|
194
226
|
}
|
|
@@ -1,33 +1,17 @@
|
|
|
1
1
|
import compose from 'docker-compose';
|
|
2
|
-
import { fetchAppReleaseHistory } from 'alwaysai/lib/infrastructure';
|
|
3
2
|
import { JsSpawner } from 'alwaysai/lib/util';
|
|
4
3
|
|
|
5
4
|
import { runDockerLogin } from '../docker/docker-cmd';
|
|
6
|
-
import { getAppDir, requireAppInstalled } from './utils';
|
|
5
|
+
import { getAppDir, requireAppInstalled, requireAppReady } from './utils';
|
|
7
6
|
import {
|
|
8
7
|
ServiceStatusPacket,
|
|
9
8
|
AppStatePacket,
|
|
10
9
|
keyMirrors,
|
|
11
|
-
AppStateValue
|
|
10
|
+
AppStateValue
|
|
12
11
|
} from '@alwaysai/device-agent-schemas';
|
|
13
12
|
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
14
13
|
import { logger } from '../util/logger';
|
|
15
14
|
|
|
16
|
-
export async function listAppReleases(props: { projectId: string }) {
|
|
17
|
-
const { projectId } = props;
|
|
18
|
-
const releaseHistory = await fetchAppReleaseHistory(projectId);
|
|
19
|
-
return releaseHistory;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export async function listAppLatestRelease(props: { projectId: string }) {
|
|
23
|
-
const { projectId } = props;
|
|
24
|
-
const releaseHistory = await fetchAppReleaseHistory(projectId);
|
|
25
|
-
if (releaseHistory.length >= 1) {
|
|
26
|
-
return releaseHistory[0]['releaseHash'];
|
|
27
|
-
}
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
15
|
export async function getAppStatus(props: {
|
|
32
16
|
projectId: string;
|
|
33
17
|
}): Promise<AppStatePacket> {
|
|
@@ -38,7 +22,7 @@ export async function getAppStatus(props: {
|
|
|
38
22
|
|
|
39
23
|
const appDetails = {
|
|
40
24
|
projectId,
|
|
41
|
-
version: await AgentConfigFile().getAppVersion({ projectId })
|
|
25
|
+
version: await AgentConfigFile().getAppVersion({ projectId })
|
|
42
26
|
};
|
|
43
27
|
if (!(await AgentConfigFile().isAppReady({ projectId }))) {
|
|
44
28
|
// App is being installed or updated
|
|
@@ -53,7 +37,7 @@ export async function getAppStatus(props: {
|
|
|
53
37
|
|
|
54
38
|
if (status.exitCode !== 0) {
|
|
55
39
|
throw new Error(
|
|
56
|
-
`Failed to get application status! stdout=${status.out} stderr=${status.err}
|
|
40
|
+
`Failed to get application status! stdout=${status.out} stderr=${status.err}`
|
|
57
41
|
);
|
|
58
42
|
}
|
|
59
43
|
|
|
@@ -64,7 +48,7 @@ export async function getAppStatus(props: {
|
|
|
64
48
|
const containerId = await spawner.run({
|
|
65
49
|
exe: 'docker-compose',
|
|
66
50
|
cwd: appDir,
|
|
67
|
-
args: ['ps', '-q', name]
|
|
51
|
+
args: ['ps', '-q', name]
|
|
68
52
|
});
|
|
69
53
|
if (containerId === '') {
|
|
70
54
|
// The service was not yet started or failed to start
|
|
@@ -72,7 +56,7 @@ export async function getAppStatus(props: {
|
|
|
72
56
|
} else {
|
|
73
57
|
const slashContainerName = await spawner.run({
|
|
74
58
|
exe: 'docker',
|
|
75
|
-
args: ['inspect', '-f', '{{.Name}}', containerId]
|
|
59
|
+
args: ['inspect', '-f', '{{.Name}}', containerId]
|
|
76
60
|
});
|
|
77
61
|
const containerName = slashContainerName.substring(1);
|
|
78
62
|
|
|
@@ -102,8 +86,8 @@ export async function getAppStatus(props: {
|
|
|
102
86
|
`Unable to find container for ${name} in ${JSON.stringify(
|
|
103
87
|
status.data.services,
|
|
104
88
|
null,
|
|
105
|
-
2
|
|
106
|
-
)}
|
|
89
|
+
2
|
|
90
|
+
)}`
|
|
107
91
|
);
|
|
108
92
|
}
|
|
109
93
|
}
|
|
@@ -117,7 +101,7 @@ export async function getAppLogs(props: {
|
|
|
117
101
|
args?: string[];
|
|
118
102
|
}): Promise<NodeJS.ReadableStream> {
|
|
119
103
|
const { projectId, services, args } = props;
|
|
120
|
-
await
|
|
104
|
+
await requireAppReady({ projectId });
|
|
121
105
|
|
|
122
106
|
const appDir = getAppDir(projectId);
|
|
123
107
|
|
|
@@ -134,7 +118,7 @@ export async function getAppLogs(props: {
|
|
|
134
118
|
return await JsSpawner().runStreaming({
|
|
135
119
|
exe: 'docker-compose',
|
|
136
120
|
args: ['logs', '-f', ...argsList, ...serviceList],
|
|
137
|
-
cwd: appDir
|
|
121
|
+
cwd: appDir
|
|
138
122
|
});
|
|
139
123
|
}
|
|
140
124
|
|
|
@@ -143,24 +127,24 @@ export async function startApp(props: {
|
|
|
143
127
|
dockerLoginToken?: string;
|
|
144
128
|
}): Promise<void> {
|
|
145
129
|
const { projectId, dockerLoginToken } = props;
|
|
146
|
-
await
|
|
130
|
+
await requireAppReady({ projectId });
|
|
147
131
|
|
|
148
132
|
const appDir = getAppDir(projectId);
|
|
149
133
|
if (dockerLoginToken !== undefined) {
|
|
150
134
|
const result = await runDockerLogin({ token: dockerLoginToken });
|
|
151
|
-
logger.
|
|
135
|
+
logger.debug(`docker login: ${result}`);
|
|
152
136
|
}
|
|
153
137
|
|
|
154
138
|
// TODO: Check if app is running
|
|
155
139
|
// Start app
|
|
156
140
|
const upOut = await compose.upAll({ cwd: appDir });
|
|
157
|
-
logger.
|
|
141
|
+
logger.debug(`docker-compose up: ${JSON.stringify(upOut, null, 2)}`);
|
|
158
142
|
if (upOut.exitCode !== 0) {
|
|
159
143
|
throw new Error(
|
|
160
|
-
`Failed to start application! stdout=${upOut.out} stderr=${upOut.err}
|
|
144
|
+
`Failed to start application! stdout=${upOut.out} stderr=${upOut.err}`
|
|
161
145
|
);
|
|
162
146
|
}
|
|
163
|
-
logger.info(
|
|
147
|
+
logger.info(`Started ${projectId}`);
|
|
164
148
|
}
|
|
165
149
|
|
|
166
150
|
export async function stopApp(props: { projectId: string }): Promise<void> {
|
|
@@ -168,16 +152,21 @@ export async function stopApp(props: { projectId: string }): Promise<void> {
|
|
|
168
152
|
await requireAppInstalled({ projectId });
|
|
169
153
|
|
|
170
154
|
const appDir = getAppDir(projectId);
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
);
|
|
155
|
+
const appStatus = await getAppStatus({ projectId });
|
|
156
|
+
const appIsRunning =
|
|
157
|
+
appStatus.services.length &&
|
|
158
|
+
appStatus.services[0].state !== keyMirrors.appState.stopped;
|
|
159
|
+
if (appIsRunning) {
|
|
160
|
+
// Stop app
|
|
161
|
+
const output = await compose.down({ cwd: appDir });
|
|
162
|
+
logger.debug(`docker-compose down: ${JSON.stringify(output, null, 2)}`);
|
|
163
|
+
if (output.exitCode !== 0) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Failed to stop application! stdout=${output.out} stderr=${output.err}`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
logger.info(`Stopped ${projectId}`);
|
|
179
169
|
}
|
|
180
|
-
logger.info('Stopped', projectId);
|
|
181
170
|
}
|
|
182
171
|
|
|
183
172
|
export async function restartApp(props: { projectId: string }): Promise<void> {
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import compose from 'docker-compose';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
DOCKER_COMPOSE_FILE,
|
|
6
|
+
TARGET_JSON_FILE_NAME
|
|
7
|
+
} from 'alwaysai/lib/constants';
|
|
5
8
|
|
|
6
9
|
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
+
import { fetchWithTimeout } from '../util/fetch-with-timeout';
|
|
11
|
+
import {
|
|
12
|
+
getDockerComposeCmdForApp,
|
|
13
|
+
TargetJsonFile,
|
|
14
|
+
updateDockerComposeTargetHw,
|
|
15
|
+
writeDockerComposeFile,
|
|
16
|
+
writeStandaloneDockerfile
|
|
17
|
+
} from 'alwaysai/lib/core/app';
|
|
10
18
|
import { runInDir } from '../util/run-in-dir';
|
|
11
|
-
import { JsSpawner } from 'alwaysai/lib/util';
|
|
12
19
|
import { logger } from '../util/logger';
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
import { APP_ROOT } from '../util/directories';
|
|
21
|
+
import { JsSpawner } from 'alwaysai/lib/util/spawner';
|
|
15
22
|
|
|
16
23
|
export function getAppDir(projectId: string): string {
|
|
17
24
|
return path.join(APP_ROOT, projectId);
|
|
@@ -23,6 +30,11 @@ export async function requireAppInstalled(props: { projectId: string }) {
|
|
|
23
30
|
if (!(await AgentConfigFile().isAppPresent({ projectId }))) {
|
|
24
31
|
throw new Error('Application is not installed');
|
|
25
32
|
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function requireAppReady(props: { projectId: string }) {
|
|
36
|
+
const { projectId } = props;
|
|
37
|
+
await requireAppInstalled({ projectId });
|
|
26
38
|
if (!(await AgentConfigFile().isAppReady({ projectId }))) {
|
|
27
39
|
throw new Error('Application is not done installing or updating');
|
|
28
40
|
}
|
|
@@ -32,46 +44,71 @@ export async function buildApp(props: { appDir: string }) {
|
|
|
32
44
|
const { appDir } = props;
|
|
33
45
|
|
|
34
46
|
// Build standalone image and docker-compose
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
if (
|
|
47
|
+
const targetJsonFile = TargetJsonFile(appDir);
|
|
48
|
+
const targetCfg = targetJsonFile.read();
|
|
49
|
+
if (targetCfg.targetProtocol !== 'docker:') {
|
|
38
50
|
throw new Error(`${TARGET_JSON_FILE_NAME} is not properly configured!`);
|
|
39
51
|
}
|
|
40
52
|
await runInDir(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
async () => {
|
|
54
|
+
await writeStandaloneDockerfile();
|
|
55
|
+
const spawner = JsSpawner();
|
|
56
|
+
const targetHardware = targetCfg.targetHardware;
|
|
57
|
+
if (await spawner.exists(DOCKER_COMPOSE_FILE)) {
|
|
58
|
+
await updateDockerComposeTargetHw({
|
|
59
|
+
targetHardware
|
|
60
|
+
});
|
|
61
|
+
} else {
|
|
62
|
+
await writeDockerComposeFile({
|
|
63
|
+
spawner: JsSpawner(),
|
|
64
|
+
cmd: await getDockerComposeCmdForApp({ targetHardware })
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
[],
|
|
69
|
+
appDir
|
|
53
70
|
);
|
|
54
71
|
|
|
55
72
|
const buildOut = await compose.buildAll({ cwd: appDir });
|
|
56
|
-
logger.
|
|
73
|
+
logger.debug(`docker-compose build: ${JSON.stringify(buildOut, null, 2)}`);
|
|
57
74
|
if (buildOut.exitCode !== 0) {
|
|
58
75
|
throw new Error(
|
|
59
|
-
`Failed to build application! stdout=${buildOut.out} stderr=${buildOut.err}
|
|
76
|
+
`Failed to build application! stdout=${buildOut.out} stderr=${buildOut.err}`
|
|
60
77
|
);
|
|
61
78
|
}
|
|
62
79
|
}
|
|
63
80
|
|
|
81
|
+
class HTTPResponseError extends Error {
|
|
82
|
+
public response;
|
|
83
|
+
constructor(response) {
|
|
84
|
+
super(`HTTP Error Response: ${response.status} ${response.statusText}`);
|
|
85
|
+
this.response = response;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const checkStatus = (response) => {
|
|
90
|
+
if (response.ok) {
|
|
91
|
+
// response.status >= 200 && response.status < 300
|
|
92
|
+
return response;
|
|
93
|
+
} else {
|
|
94
|
+
throw new HTTPResponseError(response);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
64
98
|
export async function downloadPackageUsingPresignedUrl(props: {
|
|
65
99
|
localDest: string;
|
|
66
100
|
presignedUrl: string;
|
|
67
101
|
}): Promise<void> {
|
|
68
|
-
logger.info('downloading URL');
|
|
69
102
|
const { localDest, presignedUrl } = props;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
103
|
+
logger.debug(`Downloading package from ${presignedUrl}`);
|
|
104
|
+
let response: any;
|
|
105
|
+
try {
|
|
106
|
+
response = await fetchWithTimeout(presignedUrl);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
const errorBody =
|
|
109
|
+
error.type === 'aborted' ? error : await error.response.text();
|
|
73
110
|
throw new Error(
|
|
74
|
-
`
|
|
111
|
+
`downloadPackageUsingPresignedUrl: Error=${error}\n${errorBody}`
|
|
75
112
|
);
|
|
76
113
|
}
|
|
77
114
|
|
|
@@ -84,12 +121,3 @@ export async function downloadPackageUsingPresignedUrl(props: {
|
|
|
84
121
|
stream.on('error', reject);
|
|
85
122
|
});
|
|
86
123
|
}
|
|
87
|
-
|
|
88
|
-
export async function getAppConfig(project: string) {
|
|
89
|
-
const appCfgPath = path.join(getAppDir(project), 'alwaysai.app.json');
|
|
90
|
-
if (!fs.existsSync(appCfgPath)) {
|
|
91
|
-
throw new Error(`Application config not found for project ${project}`);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return JSON.parse(await JsSpawner().readFile(appCfgPath));
|
|
95
|
-
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { AppInstallStatusPacket } from '@alwaysai/device-agent-schemas';
|
|
2
|
+
import { AppInstallStatusValue } from '@alwaysai/device-agent-schemas/lib/constants';
|
|
3
|
+
|
|
4
|
+
export class AppInstallStatus {
|
|
5
|
+
private appReleaseHash: string;
|
|
6
|
+
private status: AppInstallStatusValue;
|
|
7
|
+
private message?: string;
|
|
8
|
+
|
|
9
|
+
constructor(appReleaseHash, status, message?) {
|
|
10
|
+
this.appReleaseHash = appReleaseHash;
|
|
11
|
+
this.status = status;
|
|
12
|
+
this.message = message;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public getAppReleaseHash() {
|
|
16
|
+
return this.appReleaseHash;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public update(status, message?) {
|
|
20
|
+
this.status = status;
|
|
21
|
+
this.message = message;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public getStatusPacket(): AppInstallStatusPacket {
|
|
25
|
+
return {
|
|
26
|
+
status: this.status,
|
|
27
|
+
message: this.message,
|
|
28
|
+
appReleaseHash: this.appReleaseHash
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class AppInstallStatusManager {
|
|
34
|
+
private appList: AppInstallStatus[] = [];
|
|
35
|
+
|
|
36
|
+
public update(appReleaseHash, status, message?) {
|
|
37
|
+
// Update status if existing
|
|
38
|
+
// TODO: Reimplement this as a map
|
|
39
|
+
for (const app of this.appList) {
|
|
40
|
+
if (app.getAppReleaseHash() === appReleaseHash) {
|
|
41
|
+
app.update(status, message);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// App was not found, so add to list
|
|
46
|
+
const appInstallStatus = new AppInstallStatus(
|
|
47
|
+
appReleaseHash,
|
|
48
|
+
status,
|
|
49
|
+
message
|
|
50
|
+
);
|
|
51
|
+
this.appList.push(appInstallStatus);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public getStatusPacket(appReleaseHash) {
|
|
55
|
+
for (const app of this.appList) {
|
|
56
|
+
if (app.getAppReleaseHash() === appReleaseHash) {
|
|
57
|
+
return app.getStatusPacket();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
throw new Error(`No status for ${appReleaseHash}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { logger } from 'alwaysai/lib/util';
|
|
2
|
+
import { getIoTCoreEndpointUrl } from '../infrastructure/urls';
|
|
3
|
+
import { rmBootstrapCertsAndClose } from '../util/clean-certs';
|
|
4
|
+
import {
|
|
5
|
+
BOOTSTRAP_DEVICE_PRIVATE_KEY_FILE_PATH,
|
|
6
|
+
BOOTSTRAP_DEVICE_CERTIFICATE_FILE_PATH,
|
|
7
|
+
AWS_ROOT_CERTIFICATE_FILE_PATH
|
|
8
|
+
} from '../util/directories';
|
|
9
|
+
import { getDeviceUuid } from '../util/get-device-id';
|
|
10
|
+
import { BootstrapAgent } from './device-agent';
|
|
11
|
+
|
|
12
|
+
export function bootstrapProvision() {
|
|
13
|
+
setTimeout(rmBootstrapCertsAndClose, 60000);
|
|
14
|
+
|
|
15
|
+
const clientId = getDeviceUuid();
|
|
16
|
+
const bootstrapConfig = {
|
|
17
|
+
keyPath: BOOTSTRAP_DEVICE_PRIVATE_KEY_FILE_PATH(),
|
|
18
|
+
certPath: BOOTSTRAP_DEVICE_CERTIFICATE_FILE_PATH(),
|
|
19
|
+
caPath: AWS_ROOT_CERTIFICATE_FILE_PATH,
|
|
20
|
+
clientId,
|
|
21
|
+
host: getIoTCoreEndpointUrl()
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const bootstrapAgent = new BootstrapAgent(bootstrapConfig);
|
|
25
|
+
bootstrapAgent.subscribeToAllTopics();
|
|
26
|
+
|
|
27
|
+
bootstrapAgent.publishMessage('$aws/certificates/create/json', '');
|
|
28
|
+
|
|
29
|
+
bootstrapAgent.device.on('connect', () => {
|
|
30
|
+
logger.info('Your device is being provisioned');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
bootstrapAgent.device.on('message', (topic: string, payload: string) => {
|
|
34
|
+
bootstrapAgent.handleAwsCertificateTopics(topic, payload);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
bootstrapAgent.device.on('packetsend', (packet: any) => {
|
|
38
|
+
logger.debug(`Sending packet: ${JSON.stringify({ packet }, null, 2)}`);
|
|
39
|
+
});
|
|
40
|
+
}
|