Skip to content
This repository was archived by the owner on Aug 5, 2024. It is now read-only.

Commit 99f6d06

Browse files
authored
Localhost (#12)
* support .env files * reorg services * formatting * move stuff out of storage * avoid import * * use azurite sim * plug azurite * error messages * adding local.env * start local * dev by default * updated docs * docs * docs
1 parent 62317b6 commit 99f6d06

19 files changed

+1382
-141
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ pkg.zip
1414
tmp
1515
*.parameters.json
1616
debug.mjs
17-
pkgtmp
17+
pkgtmp
18+
.azurite

README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
This project contains a prototype development gateway implementation
44
for the built-in DeviceScript cloud integration.
55

6+
The gateway can be run locally (no cloud dependencies) or deployed to Azure.
7+
68
- [Read the documentation](https://microsoft.github.io/devicescript/developer/gateway)
79

810
| :exclamation: This implementation is for prototyping only and not meant for production. |
@@ -12,12 +14,32 @@ for the built-in DeviceScript cloud integration.
1214

1315
Make sure to follow the provisioning steps in the documentation before trying to run locally.
1416

15-
- start a local instance using
17+
- start azurite in a terminal
1618

19+
```bash
20+
yarn azurite
1721
```
22+
23+
- start a local instance using azurite
24+
25+
```bash
1826
yarn dev
1927
```
2028

29+
- after running head to http://127.0.0.1:7071/swagger/ or otherwise the live site
30+
- Click Authorize
31+
- Use user/password `devstoreaccount1:Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==`
32+
33+
## Azure services
34+
35+
Make sure to follow the provisioning steps in the documentation before trying to run locally.
36+
37+
- start a local instance using Azure services
38+
39+
```
40+
yarn dev:azure
41+
```
42+
2143
- after running head to http://127.0.0.1:7071/swagger/ or otherwise the live site
2244
- Click Authorize
2345
- Use user/password from the `passwords` secret in the key vault

dev.mjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import dotenv from "dotenv"
2-
const out = dotenv.config()
3-
if (out.error)
4-
throw out.error
2+
const azure = process.argv.includes("--azure")
3+
const out = dotenv.config({ path: azure ? "./.env" : "./local.env" })
4+
if (out.error) throw out.error
55
import("./dist/src/index.js")

devicescript

Submodule devicescript updated 233 files

local.env

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
WEBSITE_SITE_NAME="local"
2+
WEBSITE_HOSTNAME=127.0.0.1:7071
3+
DEVS_LOCALHOST = "1"
4+
DEVS_STORAGE_CONNECTION_STRING = "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;"
5+
DEVS_PASSWORDS_SECRET="DEVS_LOCAL_USER_PASSWORD"
6+
DEVS_LOCAL_USER_PASSWORD = "devstoreaccount1:Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
7+
DEVS_SWAGGER_URL="http://127.0.0.1:7071/swagger/"

package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
"build": "tsc",
1818
"watch": "tsc -w",
1919
"start": "node dist/src/index.js",
20-
"local": "node dev.mjs",
21-
"dev": "yarn build && yarn local",
20+
"azurite": "azurite --location ./.azurite --debug ./.azurite/debug.log",
21+
"dev": "yarn build && node dev.mjs --local",
22+
"dev:azure": "yarn build && node dev.mjs --azure",
2223
"logs": "node infra/logs.mjs",
2324
"deploy": "node infra/zipdeploy.mjs"
2425
},
@@ -36,6 +37,7 @@
3637
"applicationinsights": "^2.5.0",
3738
"fastify": "^4.9.2",
3839
"http-errors": "^2.0.0",
40+
"json5": "^2.2.3",
3941
"node-fetch": "^2.6.7",
4042
"openapi-types": "^12.1.0",
4143
"pino-pretty": "^9.1.1",
@@ -48,11 +50,12 @@
4850
"@types/node": "16.x",
4951
"@types/ws": "^8.5.3",
5052
"archiver": "^5.3.1",
53+
"azurite": "^3.23.0",
5154
"dotenv": "^16.0.3",
5255
"libsodium-wrappers": "^0.7.11",
5356
"open": "^8.4.2",
5457
"typescript": "^4.0.0",
5558
"xmlbuilder2": "^3.0.2",
5659
"zx": "^7.2.0"
5760
}
58-
}
61+
}

src/apidevices.ts

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,30 @@
11
import { randomBytes } from "crypto"
22
import { FastifyInstance, FastifyRequest } from "fastify"
3-
import * as storage from "./storage"
43
import {
54
checkString,
65
displayName,
76
sanitizeDeviceId,
7+
selfUrl,
88
throwStatus,
99
tryParseJSON,
1010
} from "./util"
1111
import { DeviceId, DeviceInfo, FromDeviceMessage } from "./schema"
1212
import { wsskConnString } from "./wssk"
1313
import { fullDeviceId, pingDevice, pubToDevice } from "./devutil"
1414
import { fwdSockConnSettings } from "./fwdsock"
15+
import {
16+
addMessageHook,
17+
createDevice,
18+
deleteDevice,
19+
deleteMessageHook,
20+
deviceStats,
21+
getDevice,
22+
getDeviceList,
23+
getScript,
24+
listMessageHooks,
25+
stringifyMeta,
26+
updateDevice,
27+
} from "./storage"
1528

1629
const CONNECTED_TIMEOUT = 2 * 60 * 1000
1730
export const MAX_WSSK_SIZE = 230 // for to-device JSON and binary messages
@@ -29,12 +42,12 @@ function externalDevice(info: DeviceInfo) {
2942
deployedHash: info.deployedHash,
3043
lastAct: info.lastAct ? new Date(info.lastAct).toISOString() : "",
3144
meta: tryParseJSON(info.metaJSON),
32-
stats: storage.deviceStats(info),
45+
stats: deviceStats(info),
3346
}
3447
}
3548

3649
async function singleDevice(id: DeviceId) {
37-
const dev = await storage.getDevice(id)
50+
const dev = await getDevice(id)
3851
if (!dev) throwStatus(404, "no such device")
3952
return externalDevice(dev)
4053
}
@@ -53,7 +66,7 @@ export async function getDeviceFromFullPath(req: FastifyRequest) {
5366
if (!(typeof part == "string" && /^\w{2,32}$/.test(part)))
5467
throwStatus(400, "invalid partitionId: " + part)
5568
const devid = getDeviceIdFromParams(req, part)
56-
const dev = await storage.getDevice(devid)
69+
const dev = await getDevice(devid)
5770
if (dev == null) throwStatus(404, "no such device: " + devid.rowKey)
5871
return dev
5972
}
@@ -69,10 +82,10 @@ function sanitizeDeviceIdOrThrow(
6982
}
7083

7184
async function addDevice(id: DeviceId) {
72-
let dev = await storage.getDevice(id)
85+
let dev = await getDevice(id)
7386
if (!dev) {
7487
console.log(`creating device ${fullDeviceId(id)}`)
75-
dev = await storage.createDevice(id, {
88+
dev = await createDevice(id, {
7689
key: randomBytes(32).toString("base64"),
7790
})
7891
}
@@ -126,11 +139,7 @@ async function patchDevice(id: DeviceId, req: FastifyRequest) {
126139
if (scriptId != undefined && scriptId != "") {
127140
checkString(scriptId)
128141
try {
129-
const scr = await storage.getScript(
130-
req.partition,
131-
scriptId,
132-
scriptVersion
133-
)
142+
const scr = await getScript(req.partition, scriptId, scriptVersion)
134143
// default to latest version
135144
if (!scriptVersion) scriptVersion = scr.version
136145
} catch (e: any) {
@@ -145,9 +154,9 @@ async function patchDevice(id: DeviceId, req: FastifyRequest) {
145154
}
146155
}
147156

148-
await storage.updateDevice(id, d => {
157+
await updateDevice(id, d => {
149158
if (name != undefined) d.name = name
150-
if (meta != undefined) d.metaJSON = storage.stringifyMeta(meta)
159+
if (meta != undefined) d.metaJSON = stringifyMeta(meta)
151160
if (scriptId != undefined) {
152161
d.scriptId = scriptId
153162
d.scriptVersion = scriptVersion
@@ -204,31 +213,31 @@ export async function initHubRoutes(server: FastifyInstance) {
204213
if (method) validateMethod(method)
205214
if (typeof url != "string" || !/^https:\/\//.test(url))
206215
throwStatus(418, "invalid URL")
207-
const key = await storage.addMessageHook(req.partition, {
216+
const key = await addMessageHook(req.partition, {
208217
deviceId,
209218
method,
210219
url,
211220
})
212221
return reply
213222
.status(201)
214-
.headers({ location: storage.selfUrl() + "/hooks/" + key })
223+
.headers({ location: selfUrl() + "/hooks/" + key })
215224
.send({ id: key })
216225
})
217226

218227
server.get("/hooks", async req => {
219-
return await storage.listMessageHooks(req.partition)
228+
return await listMessageHooks(req.partition)
220229
})
221230

222231
server.delete<{ Params: { hookId: string } }>(
223232
"/hooks/:hookId",
224233
async req => {
225-
await storage.deleteMessageHook(req.partition, req.params.hookId)
234+
await deleteMessageHook(req.partition, req.params.hookId)
226235
return {}
227236
}
228237
)
229238

230239
server.get("/devices", async req => {
231-
const infos = await storage.getDeviceList(req.partition)
240+
const infos = await getDeviceList(req.partition)
232241
return infos.map(externalDevice)
233242
})
234243

@@ -239,13 +248,13 @@ export async function initHubRoutes(server: FastifyInstance) {
239248

240249
server.get("/devices/:deviceId/fwd", async req => {
241250
const devid = getDeviceIdFromParams(req)
242-
const dev = await storage.getDevice(devid)
251+
const dev = await getDevice(devid)
243252
return fwdSockConnSettings(dev, "devfwd")
244253
})
245254

246255
server.get("/devices/:deviceId/logs", async req => {
247256
const devid = getDeviceIdFromParams(req)
248-
const dev = await storage.getDevice(devid)
257+
const dev = await getDevice(devid)
249258
return fwdSockConnSettings(dev, "devlogs")
250259
})
251260

@@ -256,7 +265,7 @@ export async function initHubRoutes(server: FastifyInstance) {
256265

257266
server.delete("/devices/:deviceId", async req => {
258267
const devid = getDeviceIdFromParams(req)
259-
await storage.deleteDevice(devid)
268+
await deleteDevice(devid)
260269
return {}
261270
})
262271
}

src/apiscripts.ts

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import { FastifyInstance, FastifyRequest } from "fastify"
2-
import * as storage from "./storage"
32
import { checkString, throwStatus } from "./util"
3+
import {
4+
ScriptBody,
5+
ScriptProperties,
6+
createScript,
7+
deleteScript,
8+
getScript,
9+
getScriptBody,
10+
getScriptVersions,
11+
listScripts,
12+
updateScript,
13+
} from "./storage"
414

515
function checkScriptId(id: string) {
616
if (typeof id != "string" || !/^\d{10}[a-zA-Z]{12}$/.test(id))
@@ -9,15 +19,15 @@ function checkScriptId(id: string) {
919

1020
export async function initScriptRoutes(server: FastifyInstance) {
1121
server.get("/scripts", async req => {
12-
return await storage.listScripts(req.partition)
22+
return await listScripts(req.partition)
1323
})
1424

15-
async function getScript(req: FastifyRequest) {
25+
async function fetchScript(req: FastifyRequest) {
1626
const scriptId = (req.params as any).scriptId
1727
checkScriptId(scriptId)
1828
const verId = parseInt((req.params as any).version) || undefined
1929
try {
20-
return await storage.getScript(req.partition, scriptId, verId)
30+
return await getScript(req.partition, scriptId, verId)
2131
} catch (e: any) {
2232
if (e.statusCode == 404)
2333
throwStatus(
@@ -28,44 +38,44 @@ export async function initScriptRoutes(server: FastifyInstance) {
2838
}
2939
}
3040

31-
server.get("/scripts/:scriptId", getScript)
41+
server.get("/scripts/:scriptId", fetchScript)
3242
server.get("/scripts/:scriptId/body", async req => {
33-
const scr = await getScript(req)
34-
return await storage.getScriptBody(scr.id, scr.version)
43+
const scr = await fetchScript(req)
44+
return await getScriptBody(scr.id, scr.version)
3545
})
3646

3747
server.put("/scripts/:scriptId/body", async req => {
38-
const scr = await getScript(req)
48+
const scr = await fetchScript(req)
3949
const body = verifyBody(req.body)
40-
return await storage.updateScript(scr, { body })
50+
return await updateScript(scr, { body })
4151
})
4252

4353
server.get("/scripts/:scriptId/versions", async req => {
44-
const scr = await getScript(req)
45-
const headers = await storage.getScriptVersions(scr)
54+
const scr = await fetchScript(req)
55+
const headers = await getScriptVersions(scr)
4656
return { headers }
4757
})
4858

49-
server.get("/scripts/:scriptId/versions/:version", getScript)
59+
server.get("/scripts/:scriptId/versions/:version", fetchScript)
5060
server.get("/scripts/:scriptId/versions/:version/body", async req => {
51-
const scr = await getScript(req)
52-
return await storage.getScriptBody(scr.id, scr.version)
61+
const scr = await fetchScript(req)
62+
return await getScriptBody(scr.id, scr.version)
5363
})
5464

55-
function verifyBody(body: any): storage.ScriptBody {
65+
function verifyBody(body: any): ScriptBody {
5666
if (typeof body != "object" || Array.isArray(body))
5767
throwStatus(412, "invalid body type")
5868
if (JSON.stringify(body).length > 5 * 1024 * 1024)
5969
throwStatus(413, "body too large")
60-
const b = body as storage.ScriptBody
70+
const b = body as ScriptBody
6171
if (!b.program || typeof b.program?.binary?.hex != "string")
6272
throwStatus(418, "invalid body format")
6373
return body
6474
}
6575

6676
function getScriptData(req: FastifyRequest) {
6777
const reqbody: any = req.body
68-
const props: storage.ScriptProperties = {
78+
const props: ScriptProperties = {
6979
name: reqbody.name,
7080
meta: reqbody.meta,
7181
body: reqbody.body,
@@ -82,19 +92,19 @@ export async function initScriptRoutes(server: FastifyInstance) {
8292
}
8393

8494
server.patch("/scripts/:scriptId", async req => {
85-
const scr = await getScript(req)
95+
const scr = await fetchScript(req)
8696
const updates = getScriptData(req)
87-
return await storage.updateScript(scr, updates)
97+
return await updateScript(scr, updates)
8898
})
8999

90100
server.delete("/scripts/:scriptId", async req => {
91-
const scr = await getScript(req)
92-
return await storage.deleteScript(req.partition, scr.id)
101+
const scr = await fetchScript(req)
102+
return await deleteScript(req.partition, scr.id)
93103
})
94104

95105
server.post("/scripts", async req => {
96106
const props = getScriptData(req)
97107
if (!props.body) throwStatus(400, "no body")
98-
return await storage.createScript(req.partition, props)
108+
return await createScript(req.partition, props)
99109
})
100110
}

0 commit comments

Comments
 (0)