@mega-yfue/eufy-sdk 0.4.0-beta.3 → 0.4.0-beta.5
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/client/eufy-mega.d.ts +4 -1
- package/dist/index.js +53 -27
- package/dist/index.js.map +2 -2
- package/dist/transport/mqtt/secure-mqtt.d.ts +20 -13
- package/package.json +1 -1
|
@@ -871,7 +871,10 @@ export declare class EufyMega extends EventEmitter {
|
|
|
871
871
|
* one — so this never overwrites a channel a later login already brought up.
|
|
872
872
|
*/
|
|
873
873
|
private startPush;
|
|
874
|
-
/**
|
|
874
|
+
/**
|
|
875
|
+
* Admit only exact, account-known devices with resolved snapshot evidence into the passive store; a
|
|
876
|
+
* station-only candidate is admitted under the event's device serial.
|
|
877
|
+
*/
|
|
875
878
|
private observeStoredImage;
|
|
876
879
|
/**
|
|
877
880
|
* Tear down every realtime channel: close the secure-MQTT transport, all P2P sessions, and the FCM
|
package/dist/index.js
CHANGED
|
@@ -2125,6 +2125,10 @@ function solixUserTopics(appName, userId) {
|
|
|
2125
2125
|
|
|
2126
2126
|
// dist/transport/mqtt/secure-mqtt.js
|
|
2127
2127
|
var SUBACK_FAILURE = 128;
|
|
2128
|
+
function isRefusal(err) {
|
|
2129
|
+
const granted = err?.packet?.granted;
|
|
2130
|
+
return Array.isArray(granted) && granted.some((g) => typeof g === "number" && (g & SUBACK_FAILURE) !== 0);
|
|
2131
|
+
}
|
|
2128
2132
|
function isNotAuthorized(err) {
|
|
2129
2133
|
return err instanceof Error && /connection refused/i.test(err.message) && /not authori[sz]ed/i.test(err.message);
|
|
2130
2134
|
}
|
|
@@ -2227,19 +2231,16 @@ var SecureMqtt = class extends EventEmitter {
|
|
|
2227
2231
|
}
|
|
2228
2232
|
/**
|
|
2229
2233
|
* Subscribe every inbound leg this device's line uses (see `topics.ts` — one `/res` for most lines,
|
|
2230
|
-
* four topics for `eufy_life`).
|
|
2234
|
+
* four topics for `eufy_life` and the clean line).
|
|
2231
2235
|
*
|
|
2232
|
-
* The grants are INSPECTED, not assumed: AWS IoT
|
|
2233
|
-
* SUBACK_FAILURE
|
|
2234
|
-
*
|
|
2235
|
-
*
|
|
2236
|
-
* line that grants its state channel but refuses (say) the OTA leg still works.
|
|
2236
|
+
* The grants are INSPECTED, not assumed: AWS IoT refuses a policy-denied filter with a
|
|
2237
|
+
* {@link SUBACK_FAILURE} grant rather than failing the connection. A denied topic is reported via
|
|
2238
|
+
* `error` naming the credential scope; only an all-denied device throws, so a line that grants its
|
|
2239
|
+
* state channel but refuses (say) a business leg still works.
|
|
2237
2240
|
*/
|
|
2238
2241
|
async subscribeDevice(device) {
|
|
2239
|
-
if (!this.client)
|
|
2240
|
-
throw new Error("SecureMqtt not connected");
|
|
2241
2242
|
const topics = [...subscribeTopics(device)];
|
|
2242
|
-
const { denied } =
|
|
2243
|
+
const { denied } = await this.subscribeEach(topics);
|
|
2243
2244
|
const scope = this.o.credentials.app_name ?? "default";
|
|
2244
2245
|
if (denied.length === topics.length) {
|
|
2245
2246
|
throw new Error(`subscribe ${device.sn}: every topic denied on credential scope "${scope}" \u2014 this line's topics are granted to a different scope (${denied.join(", ")})`);
|
|
@@ -2250,25 +2251,43 @@ var SecureMqtt = class extends EventEmitter {
|
|
|
2250
2251
|
}
|
|
2251
2252
|
/**
|
|
2252
2253
|
* Subscribe to explicit topic filters, returning the topics that were granted. A scope-denied filter
|
|
2253
|
-
*
|
|
2254
|
-
*
|
|
2255
|
-
*
|
|
2254
|
+
* is dropped from the result instead of throwing — callers that need every leg check the returned
|
|
2255
|
+
* list. Used by lines whose topic vocabulary isn't the eufy `subscribeTopics` shape (e.g. Anker Solix
|
|
2256
|
+
* `dt/{app}/{pn}/{sn}`).
|
|
2256
2257
|
*/
|
|
2257
2258
|
async subscribe(topics) {
|
|
2258
|
-
|
|
2259
|
-
throw new Error("SecureMqtt not connected");
|
|
2260
|
-
return this.partitionGrants(await this.client.subscribeAsync(topics, { qos: 1 })).granted;
|
|
2259
|
+
return (await this.subscribeEach(topics)).granted;
|
|
2261
2260
|
}
|
|
2262
2261
|
/**
|
|
2263
|
-
*
|
|
2264
|
-
*
|
|
2265
|
-
*
|
|
2262
|
+
* Subscribe each filter in a SUBSCRIBE of its own and split the outcome into granted vs refused.
|
|
2263
|
+
*
|
|
2264
|
+
* One request per filter because the MQTT engine treats a SUBACK as all-or-nothing: one refused grant
|
|
2265
|
+
* rejects the whole request, and it forgets EVERY filter of that request for resubscription after a
|
|
2266
|
+
* reconnect — so a batch holding one denied leg would lose the granted legs on the next drop. Alone,
|
|
2267
|
+
* a refused filter costs only itself. A rejection that carries no SUBACK is a transport failure and
|
|
2268
|
+
* is thrown.
|
|
2269
|
+
*
|
|
2270
|
+
* The cost is one SUBSCRIBE and one SUBACK per filter instead of one per device — four for a
|
|
2271
|
+
* four-leg line. They are sent concurrently, so the wall-clock cost is one round trip. Folding them
|
|
2272
|
+
* back into one request brings back the lost resubscription.
|
|
2266
2273
|
*/
|
|
2267
|
-
|
|
2274
|
+
async subscribeEach(topics) {
|
|
2275
|
+
const client = this.client;
|
|
2276
|
+
if (!client)
|
|
2277
|
+
throw new Error("SecureMqtt not connected");
|
|
2278
|
+
const outcomes = await Promise.allSettled(topics.map((topic) => client.subscribeAsync(topic, { qos: 1 })));
|
|
2268
2279
|
const granted = [];
|
|
2269
2280
|
const denied = [];
|
|
2270
|
-
|
|
2271
|
-
|
|
2281
|
+
outcomes.forEach((outcome, i) => {
|
|
2282
|
+
const topic = topics[i];
|
|
2283
|
+
if (outcome.status === "fulfilled") {
|
|
2284
|
+
granted.push(topic);
|
|
2285
|
+
return;
|
|
2286
|
+
}
|
|
2287
|
+
if (!isRefusal(outcome.reason))
|
|
2288
|
+
throw outcome.reason;
|
|
2289
|
+
denied.push(topic);
|
|
2290
|
+
});
|
|
2272
2291
|
return { granted, denied };
|
|
2273
2292
|
}
|
|
2274
2293
|
/**
|
|
@@ -26851,7 +26870,7 @@ var EufyMega = class extends EventEmitter9 {
|
|
|
26851
26870
|
if (ev.eventName === void 0 && ev.eventType != null)
|
|
26852
26871
|
ev.eventName = detectionName(ev.eventType);
|
|
26853
26872
|
if (ev.thumbnailCandidate)
|
|
26854
|
-
void this.observeStoredImage(ev.thumbnailCandidate);
|
|
26873
|
+
void this.observeStoredImage(ev.thumbnailCandidate, ev.deviceSn);
|
|
26855
26874
|
this.emit("push", ev);
|
|
26856
26875
|
const signal2 = {
|
|
26857
26876
|
source: "push",
|
|
@@ -26900,9 +26919,16 @@ var EufyMega = class extends EventEmitter9 {
|
|
|
26900
26919
|
client.on("error", (e) => this.reportError(e));
|
|
26901
26920
|
return client;
|
|
26902
26921
|
}
|
|
26903
|
-
/**
|
|
26904
|
-
|
|
26905
|
-
|
|
26922
|
+
/**
|
|
26923
|
+
* Admit only exact, account-known devices with resolved snapshot evidence into the passive store; a
|
|
26924
|
+
* station-only candidate is admitted under the event's device serial.
|
|
26925
|
+
*/
|
|
26926
|
+
async observeStoredImage(candidate, eventDeviceSn) {
|
|
26927
|
+
if (!this.storedImages)
|
|
26928
|
+
return;
|
|
26929
|
+
const { attribution } = candidate;
|
|
26930
|
+
const deviceSn = attribution.kind === "device" ? attribution.deviceSn : attribution.kind === "station" ? eventDeviceSn : void 0;
|
|
26931
|
+
if (!deviceSn)
|
|
26906
26932
|
return;
|
|
26907
26933
|
const account = this.mega.auth?.userId;
|
|
26908
26934
|
if (!account)
|
|
@@ -26912,9 +26938,9 @@ var EufyMega = class extends EventEmitter9 {
|
|
|
26912
26938
|
await this.registry.getDevices();
|
|
26913
26939
|
if (this.mega.auth?.userId !== account)
|
|
26914
26940
|
return;
|
|
26915
|
-
const caps = this.registry.capabilitiesForDevice(
|
|
26941
|
+
const caps = this.registry.capabilitiesForDevice(deviceSn);
|
|
26916
26942
|
if (caps && hasProvidedAction(caps, "snapshotStored")) {
|
|
26917
|
-
this.storedImages.observe(
|
|
26943
|
+
this.storedImages.observe(deviceSn, candidate.url);
|
|
26918
26944
|
}
|
|
26919
26945
|
} catch (e) {
|
|
26920
26946
|
this.reportError(e);
|