@camstack/addon-import-alexa 0.2.148 → 0.2.149
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/dist/MaskShapeCanvas-B6q5RDLi.mjs +13807 -0
- package/dist/MotionZonesSettings-BD57OAIs.mjs +309 -0
- package/dist/PrivacyMaskSettings-ZxdPEelf.mjs +368 -0
- package/dist/SceneMonitorEditor--KqtKX1B.mjs +846 -0
- package/dist/_stub.js +39665 -0
- package/dist/_virtual_mf-localSharedImportMap___mfe_internal__addon_import_alexa_page-CdRL7bFZ.mjs +156 -0
- package/dist/_virtual_mf___mfe_internal__addon_import_alexa_page__loadShare___mf_0_camstack_mf_1_sdk__loadShare__.js-BmoaT3cr.mjs +25 -0
- package/dist/_virtual_mf___mfe_internal__addon_import_alexa_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-I8eNxvdp.mjs +26 -0
- package/dist/_virtual_mf___mfe_internal__addon_import_alexa_page__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-Bct5qZia.mjs +25 -0
- package/dist/_virtual_mf___mfe_internal__addon_import_alexa_page__loadShare___mf_0_trpc_mf_1_client__loadShare__.js-BHqOw8oh.mjs +25 -0
- package/dist/_virtual_mf___mfe_internal__addon_import_alexa_page__loadShare___mf_0_trpc_mf_1_react_mf_2_query__loadShare__.js-B3qxfxBT.mjs +26 -0
- package/dist/_virtual_mf___mfe_internal__addon_import_alexa_page__loadShare__react__loadShare__.js-DwXXYAlk.mjs +70 -0
- package/dist/_virtual_mf___mfe_internal__addon_import_alexa_page__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-CUiVYNMY.mjs +26 -0
- package/dist/_virtual_mf___mfe_internal__addon_import_alexa_page__loadShare__react_mf_2_dom__loadShare__.js-CIPmAyPi.mjs +25 -0
- package/dist/_virtual_mf___mfe_internal__addon_import_alexa_page__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-tXm1x5EZ.mjs +25 -0
- package/dist/addon-import-alexa.css +3 -0
- package/dist/addon.js +2211 -160
- package/dist/addon.mjs +2227 -179
- package/dist/hostInit-_k3o3MHN.mjs +129 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +2 -2
- package/dist/player-overlays-CJBC2I_E.mjs +51 -0
- package/dist/remoteEntry.js +2 -0
- package/dist/remoteEntry.ssr.js +33 -0
- package/dist/responsive-N99niSJu.mjs +125 -0
- package/dist/rolldown-runtime-HEgqtunE.mjs +20 -0
- package/dist/scene-monitor-copy-DeuPVYmS.mjs +227 -0
- package/dist/square-CnLcG0Zc.mjs +45 -0
- package/dist/trash-2-BzimUQsl.mjs +25 -0
- package/dist/ui-text-B5YO4hoJ.mjs +34 -0
- package/dist/virtualExposes-lZT06jeg.mjs +2 -0
- package/dist/virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_import_alexa_page__remoteEntry_js-CsdMNhUS.mjs +2857 -0
- package/dist/virtual_mf-exposes-ssr___mfe_internal__addon_import_alexa_page__remoteEntry_js-CViESMdd.mjs +10 -0
- package/dist/virtual_mf-exposes___mfe_internal__addon_import_alexa_page__remoteEntry_js-C3qJCjtV.mjs +27 -0
- package/i18n/en.json +6 -1
- package/i18n/it.json +6 -1
- package/package.json +26 -4
- package/python/NOTICE +4 -0
- package/python/alexa_gateway.py +199 -0
- package/python/alexa_protocol.py +110 -0
- package/python/alexa_sequences.py +74 -0
- package/python/alexa_worker.py +50 -0
- package/python/requirements.txt +3 -0
- package/python/test_alexa_protocol.py +168 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""Protocol handlers with alexapy mocked. These tests must not contact Amazon."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
import tempfile
|
|
9
|
+
import unittest
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
13
|
+
|
|
14
|
+
from alexa_protocol import Session
|
|
15
|
+
from alexa_sequences import build_announcement, build_speak_sequence
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FakeGateway:
|
|
19
|
+
def __init__(self) -> None:
|
|
20
|
+
self.password = ""
|
|
21
|
+
self.region = "EU"
|
|
22
|
+
self.login_status = "ok"
|
|
23
|
+
self.devices_payload = {
|
|
24
|
+
"devices": [
|
|
25
|
+
{
|
|
26
|
+
"id": "echo-1",
|
|
27
|
+
"name": "Kitchen",
|
|
28
|
+
"family": "ECHO",
|
|
29
|
+
"online": True,
|
|
30
|
+
"kind": "device",
|
|
31
|
+
"dnd": False,
|
|
32
|
+
"deviceType": "A3S5BH2HU6VAYF",
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
self.spoken: list[dict] = []
|
|
37
|
+
self.announced: list[dict] = []
|
|
38
|
+
|
|
39
|
+
async def login_start(self, email: str, password: str, region: str, session_path: str) -> dict:
|
|
40
|
+
self.password = password
|
|
41
|
+
self.email = email
|
|
42
|
+
self.region = region
|
|
43
|
+
self.session_path = session_path
|
|
44
|
+
result = {"status": self.login_status}
|
|
45
|
+
if self.login_status == "failed":
|
|
46
|
+
result["error"] = "login failed"
|
|
47
|
+
return result
|
|
48
|
+
|
|
49
|
+
async def login_otp(self, code: str, session_path: str = "") -> dict:
|
|
50
|
+
self.code = code
|
|
51
|
+
return {"status": "ok"}
|
|
52
|
+
|
|
53
|
+
async def devices(self) -> dict:
|
|
54
|
+
return self.devices_payload
|
|
55
|
+
|
|
56
|
+
async def speak(self, sequence: dict, customer_id: str) -> None:
|
|
57
|
+
self.spoken.append({"sequence": sequence, "customerId": customer_id})
|
|
58
|
+
|
|
59
|
+
async def announce(self, announcement: dict, customer_id: str) -> None:
|
|
60
|
+
self.announced.append(announcement)
|
|
61
|
+
|
|
62
|
+
async def session_status(self) -> dict:
|
|
63
|
+
return {"loggedIn": True, "ageSec": 12, "lastSuccessAt": 100}
|
|
64
|
+
|
|
65
|
+
def target_for(self, serial: str):
|
|
66
|
+
if serial != "echo-1":
|
|
67
|
+
return None
|
|
68
|
+
return {"id": "echo-1", "deviceType": "A3S5BH2HU6VAYF", "customerId": "cust"}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def run(session: Session, message: dict) -> dict:
|
|
72
|
+
return asyncio.run(session.handle(message))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ProtocolTests(unittest.TestCase):
|
|
76
|
+
def test_login_reads_password_from_file_not_from_json(self) -> None:
|
|
77
|
+
gateway = FakeGateway()
|
|
78
|
+
session = Session(gateway)
|
|
79
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
80
|
+
password_path = Path(directory) / "password"
|
|
81
|
+
password_path.write_text("super-secret-password", encoding="utf-8")
|
|
82
|
+
session_path = Path(directory) / "session"
|
|
83
|
+
session_path.write_bytes(b"")
|
|
84
|
+
body = {
|
|
85
|
+
"id": "1",
|
|
86
|
+
"op": "login_start",
|
|
87
|
+
"email": "a@example.com",
|
|
88
|
+
"passwordPath": str(password_path),
|
|
89
|
+
"sessionPath": str(session_path),
|
|
90
|
+
"region": "EU",
|
|
91
|
+
}
|
|
92
|
+
encoded = json.dumps(body)
|
|
93
|
+
self.assertNotIn("super-secret-password", encoded)
|
|
94
|
+
result = run(session, body)
|
|
95
|
+
self.assertTrue(result["ok"])
|
|
96
|
+
self.assertEqual(result["result"]["status"], "ok")
|
|
97
|
+
self.assertEqual(gateway.password, "super-secret-password")
|
|
98
|
+
|
|
99
|
+
def test_otp_then_devices(self) -> None:
|
|
100
|
+
gateway = FakeGateway()
|
|
101
|
+
gateway.login_status = "otp_needed"
|
|
102
|
+
session = Session(gateway)
|
|
103
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
104
|
+
password_path = Path(directory) / "password"
|
|
105
|
+
password_path.write_text("pw", encoding="utf-8")
|
|
106
|
+
started = run(
|
|
107
|
+
session,
|
|
108
|
+
{
|
|
109
|
+
"id": "1",
|
|
110
|
+
"op": "login_start",
|
|
111
|
+
"email": "a@example.com",
|
|
112
|
+
"passwordPath": str(password_path),
|
|
113
|
+
"sessionPath": str(Path(directory) / "session"),
|
|
114
|
+
"region": "NA",
|
|
115
|
+
},
|
|
116
|
+
)
|
|
117
|
+
self.assertEqual(started["result"]["status"], "otp_needed")
|
|
118
|
+
otp = run(session, {"id": "2", "op": "login_otp", "code": "123456"})
|
|
119
|
+
self.assertEqual(otp["result"]["status"], "ok")
|
|
120
|
+
devices = run(session, {"id": "3", "op": "devices"})
|
|
121
|
+
self.assertEqual(devices["result"]["devices"][0]["id"], "echo-1")
|
|
122
|
+
self.assertEqual(devices["result"]["devices"][0]["family"], "ECHO")
|
|
123
|
+
self.assertTrue(devices["result"]["devices"][0]["online"])
|
|
124
|
+
|
|
125
|
+
def test_speak_and_announce_use_sequence_builders(self) -> None:
|
|
126
|
+
gateway = FakeGateway()
|
|
127
|
+
session = Session(gateway)
|
|
128
|
+
spoken = run(
|
|
129
|
+
session,
|
|
130
|
+
{"id": "4", "op": "speak", "targets": ["echo-1"], "text": "Person at the door", "volume": 30},
|
|
131
|
+
)
|
|
132
|
+
self.assertTrue(spoken["result"]["outcomes"][0]["ok"])
|
|
133
|
+
sequence = gateway.spoken[0]["sequence"]
|
|
134
|
+
self.assertEqual(sequence["startNode"]["type"], "Alexa.DeviceControls.Volume")
|
|
135
|
+
self.assertEqual(sequence["startNode"]["next"]["type"], "Alexa.Speak")
|
|
136
|
+
self.assertEqual(
|
|
137
|
+
sequence["startNode"]["next"]["operationPayload"]["textToSpeak"],
|
|
138
|
+
"Person at the door",
|
|
139
|
+
)
|
|
140
|
+
announced = run(
|
|
141
|
+
session,
|
|
142
|
+
{"id": "5", "op": "announce", "targets": ["echo-1"], "text": "Hello", "volume": 30},
|
|
143
|
+
)
|
|
144
|
+
self.assertTrue(announced["result"]["outcomes"][0]["ok"])
|
|
145
|
+
self.assertEqual(gateway.announced[0]["content"][0]["speak"]["value"], "Hello")
|
|
146
|
+
|
|
147
|
+
def test_unknown_op_and_bad_json_shape(self) -> None:
|
|
148
|
+
session = Session(FakeGateway())
|
|
149
|
+
unknown = run(session, {"id": "9", "op": "nope"})
|
|
150
|
+
self.assertFalse(unknown["ok"])
|
|
151
|
+
self.assertIn("unknown op", unknown["error"])
|
|
152
|
+
|
|
153
|
+
def test_session_status(self) -> None:
|
|
154
|
+
session = Session(FakeGateway())
|
|
155
|
+
status = run(session, {"id": "6", "op": "session_status"})
|
|
156
|
+
self.assertEqual(status["result"]["ageSec"], 12)
|
|
157
|
+
self.assertTrue(status["result"]["loggedIn"])
|
|
158
|
+
|
|
159
|
+
def test_sequence_helpers_do_not_embed_markup(self) -> None:
|
|
160
|
+
target = {"id": "echo-1", "deviceType": "T", "customerId": "c"}
|
|
161
|
+
sequence = build_speak_sequence(target, "plain", 10, "en-GB")
|
|
162
|
+
self.assertNotIn("<", json.dumps(sequence))
|
|
163
|
+
announcement = build_announcement(target, "plain", "en-GB")
|
|
164
|
+
self.assertEqual(announcement["content"][0]["speak"]["type"], "text")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
if __name__ == "__main__":
|
|
168
|
+
unittest.main()
|