@yoooclaw/phone-notifications 1.7.2 → 1.7.3
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/index.js +167 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5856,11 +5856,96 @@ function buildDeviceAuthPayload(params) {
|
|
|
5856
5856
|
params.nonce
|
|
5857
5857
|
].join("|");
|
|
5858
5858
|
}
|
|
5859
|
-
function
|
|
5860
|
-
return resolveStateDir();
|
|
5859
|
+
function resolveClientStateDir(stateDir) {
|
|
5860
|
+
return stateDir ?? resolveStateDir();
|
|
5861
5861
|
}
|
|
5862
|
-
function
|
|
5863
|
-
|
|
5862
|
+
function ensureDir(filePath) {
|
|
5863
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
5864
|
+
}
|
|
5865
|
+
function resolveIdentityPath(stateDir) {
|
|
5866
|
+
return path.join(stateDir, "identity", "device.json");
|
|
5867
|
+
}
|
|
5868
|
+
function normalizeDeviceAuthRole(role) {
|
|
5869
|
+
return role.trim();
|
|
5870
|
+
}
|
|
5871
|
+
function normalizeDeviceAuthScopes(scopes) {
|
|
5872
|
+
const out = /* @__PURE__ */ new Set();
|
|
5873
|
+
for (const scope of scopes) {
|
|
5874
|
+
const trimmed = scope.trim();
|
|
5875
|
+
if (trimmed) {
|
|
5876
|
+
out.add(trimmed);
|
|
5877
|
+
}
|
|
5878
|
+
}
|
|
5879
|
+
return [...out].sort();
|
|
5880
|
+
}
|
|
5881
|
+
function resolveDeviceAuthPath(stateDir) {
|
|
5882
|
+
return path.join(stateDir, "identity", "device-auth.json");
|
|
5883
|
+
}
|
|
5884
|
+
function readDeviceAuthStore(filePath) {
|
|
5885
|
+
try {
|
|
5886
|
+
if (!fs.existsSync(filePath)) return null;
|
|
5887
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
5888
|
+
const parsed = JSON.parse(raw);
|
|
5889
|
+
if (parsed?.version !== 1 || typeof parsed.deviceId !== "string") return null;
|
|
5890
|
+
if (!parsed.tokens || typeof parsed.tokens !== "object") return null;
|
|
5891
|
+
return parsed;
|
|
5892
|
+
} catch {
|
|
5893
|
+
return null;
|
|
5894
|
+
}
|
|
5895
|
+
}
|
|
5896
|
+
function writeDeviceAuthStore(filePath, store) {
|
|
5897
|
+
ensureDir(filePath);
|
|
5898
|
+
fs.writeFileSync(filePath, `${JSON.stringify(store, null, 2)}
|
|
5899
|
+
`, {
|
|
5900
|
+
mode: 384
|
|
5901
|
+
});
|
|
5902
|
+
try {
|
|
5903
|
+
fs.chmodSync(filePath, 384);
|
|
5904
|
+
} catch {
|
|
5905
|
+
}
|
|
5906
|
+
}
|
|
5907
|
+
function loadDeviceAuthToken(params) {
|
|
5908
|
+
const store = readDeviceAuthStore(resolveDeviceAuthPath(params.stateDir));
|
|
5909
|
+
if (!store || store.deviceId !== params.deviceId) return null;
|
|
5910
|
+
const entry = store.tokens[normalizeDeviceAuthRole(params.role)];
|
|
5911
|
+
if (!entry || typeof entry.token !== "string") return null;
|
|
5912
|
+
return entry;
|
|
5913
|
+
}
|
|
5914
|
+
function storeDeviceAuthToken(params) {
|
|
5915
|
+
const filePath = resolveDeviceAuthPath(params.stateDir);
|
|
5916
|
+
const existing = readDeviceAuthStore(filePath);
|
|
5917
|
+
const role = normalizeDeviceAuthRole(params.role);
|
|
5918
|
+
const next = {
|
|
5919
|
+
version: 1,
|
|
5920
|
+
deviceId: params.deviceId,
|
|
5921
|
+
tokens: existing && existing.deviceId === params.deviceId && existing.tokens ? { ...existing.tokens } : {}
|
|
5922
|
+
};
|
|
5923
|
+
const entry = {
|
|
5924
|
+
token: params.token,
|
|
5925
|
+
role,
|
|
5926
|
+
scopes: normalizeDeviceAuthScopes(params.scopes),
|
|
5927
|
+
updatedAtMs: Date.now()
|
|
5928
|
+
};
|
|
5929
|
+
next.tokens[role] = entry;
|
|
5930
|
+
writeDeviceAuthStore(filePath, next);
|
|
5931
|
+
return entry;
|
|
5932
|
+
}
|
|
5933
|
+
function clearDeviceAuthToken(params) {
|
|
5934
|
+
const filePath = resolveDeviceAuthPath(params.stateDir);
|
|
5935
|
+
const store = readDeviceAuthStore(filePath);
|
|
5936
|
+
if (!store || store.deviceId !== params.deviceId) return;
|
|
5937
|
+
const role = normalizeDeviceAuthRole(params.role);
|
|
5938
|
+
if (!store.tokens[role]) return;
|
|
5939
|
+
const next = {
|
|
5940
|
+
version: 1,
|
|
5941
|
+
deviceId: store.deviceId,
|
|
5942
|
+
tokens: { ...store.tokens }
|
|
5943
|
+
};
|
|
5944
|
+
delete next.tokens[role];
|
|
5945
|
+
writeDeviceAuthStore(filePath, next);
|
|
5946
|
+
}
|
|
5947
|
+
function loadOrCreateDeviceIdentity(stateDir) {
|
|
5948
|
+
const filePath = resolveIdentityPath(stateDir);
|
|
5864
5949
|
try {
|
|
5865
5950
|
if (fs.existsSync(filePath)) {
|
|
5866
5951
|
const raw = fs.readFileSync(filePath, "utf8");
|
|
@@ -5890,6 +5975,7 @@ function loadOrCreateDeviceIdentity() {
|
|
|
5890
5975
|
...identity,
|
|
5891
5976
|
createdAtMs: Date.now()
|
|
5892
5977
|
};
|
|
5978
|
+
ensureDir(filePath);
|
|
5893
5979
|
fs.writeFileSync(filePath, `${JSON.stringify(stored, null, 2)}
|
|
5894
5980
|
`, {
|
|
5895
5981
|
mode: 384
|
|
@@ -5899,7 +5985,8 @@ function loadOrCreateDeviceIdentity() {
|
|
|
5899
5985
|
var TunnelProxy = class _TunnelProxy {
|
|
5900
5986
|
constructor(opts) {
|
|
5901
5987
|
this.opts = opts;
|
|
5902
|
-
this.
|
|
5988
|
+
this.stateDir = resolveClientStateDir(opts.stateDir);
|
|
5989
|
+
this.deviceIdentity = loadOrCreateDeviceIdentity(this.stateDir);
|
|
5903
5990
|
opts.logger.info(
|
|
5904
5991
|
`TunnelProxy: loaded device identity (deviceId=${this.deviceIdentity.deviceId})`
|
|
5905
5992
|
);
|
|
@@ -5915,6 +6002,7 @@ var TunnelProxy = class _TunnelProxy {
|
|
|
5915
6002
|
gatewayWsPending = [];
|
|
5916
6003
|
/** 设备身份,用于 Gateway connect 握手 */
|
|
5917
6004
|
deviceIdentity;
|
|
6005
|
+
stateDir;
|
|
5918
6006
|
pushGatewayPending(payload, reason) {
|
|
5919
6007
|
this.gatewayWsPending.push(payload);
|
|
5920
6008
|
this.opts.logger.info(
|
|
@@ -5932,6 +6020,65 @@ var TunnelProxy = class _TunnelProxy {
|
|
|
5932
6020
|
password
|
|
5933
6021
|
};
|
|
5934
6022
|
}
|
|
6023
|
+
loadStoredDeviceToken(role) {
|
|
6024
|
+
return loadDeviceAuthToken({
|
|
6025
|
+
stateDir: this.stateDir,
|
|
6026
|
+
deviceId: this.deviceIdentity.deviceId,
|
|
6027
|
+
role
|
|
6028
|
+
})?.token ?? void 0;
|
|
6029
|
+
}
|
|
6030
|
+
buildGatewayConnectAuth(role) {
|
|
6031
|
+
const explicitAuth = this.resolveGatewayConnectAuth();
|
|
6032
|
+
const authPassword = explicitAuth?.password?.trim() || void 0;
|
|
6033
|
+
const explicitGatewayToken = explicitAuth?.token?.trim() || void 0;
|
|
6034
|
+
const deviceToken = explicitGatewayToken ? void 0 : this.loadStoredDeviceToken(role);
|
|
6035
|
+
const authToken = explicitGatewayToken ?? deviceToken;
|
|
6036
|
+
const auth = authToken || authPassword || deviceToken ? {
|
|
6037
|
+
token: authToken,
|
|
6038
|
+
deviceToken,
|
|
6039
|
+
password: authPassword
|
|
6040
|
+
} : void 0;
|
|
6041
|
+
return {
|
|
6042
|
+
auth,
|
|
6043
|
+
authToken,
|
|
6044
|
+
authPassword,
|
|
6045
|
+
deviceToken
|
|
6046
|
+
};
|
|
6047
|
+
}
|
|
6048
|
+
storeIssuedDeviceToken(params) {
|
|
6049
|
+
const token = params.authInfo?.deviceToken;
|
|
6050
|
+
if (typeof token !== "string" || !token.trim()) {
|
|
6051
|
+
return;
|
|
6052
|
+
}
|
|
6053
|
+
const role = typeof params.authInfo?.role === "string" && params.authInfo.role.trim() ? params.authInfo.role.trim() : params.fallbackRole;
|
|
6054
|
+
const scopes = Array.isArray(params.authInfo?.scopes) ? params.authInfo.scopes.filter(
|
|
6055
|
+
(scope) => typeof scope === "string" && !!scope.trim()
|
|
6056
|
+
) : params.fallbackScopes;
|
|
6057
|
+
storeDeviceAuthToken({
|
|
6058
|
+
stateDir: this.stateDir,
|
|
6059
|
+
deviceId: this.deviceIdentity.deviceId,
|
|
6060
|
+
role,
|
|
6061
|
+
token: token.trim(),
|
|
6062
|
+
scopes
|
|
6063
|
+
});
|
|
6064
|
+
}
|
|
6065
|
+
maybeClearStoredDeviceTokenOnMismatch(code, reason) {
|
|
6066
|
+
const explicitAuth = this.resolveGatewayConnectAuth();
|
|
6067
|
+
if (explicitAuth?.token || explicitAuth?.password) {
|
|
6068
|
+
return;
|
|
6069
|
+
}
|
|
6070
|
+
if (code !== 1008 || !reason.toLowerCase().includes("device token mismatch")) {
|
|
6071
|
+
return;
|
|
6072
|
+
}
|
|
6073
|
+
clearDeviceAuthToken({
|
|
6074
|
+
stateDir: this.stateDir,
|
|
6075
|
+
deviceId: this.deviceIdentity.deviceId,
|
|
6076
|
+
role: "operator"
|
|
6077
|
+
});
|
|
6078
|
+
this.opts.logger.warn(
|
|
6079
|
+
`TunnelProxy: cleared stale stored device token after gateway mismatch (deviceId=${this.deviceIdentity.deviceId})`
|
|
6080
|
+
);
|
|
6081
|
+
}
|
|
5935
6082
|
buildLocalGatewayAuthAttempts(baseHeaders) {
|
|
5936
6083
|
const auth = this.resolveGatewayConnectAuth();
|
|
5937
6084
|
const attempts = [];
|
|
@@ -6101,12 +6248,12 @@ var TunnelProxy = class _TunnelProxy {
|
|
|
6101
6248
|
if (frame.type === "event" && frame.event === "connect.challenge") {
|
|
6102
6249
|
const challengeNonce = frame.payload?.nonce ?? "";
|
|
6103
6250
|
const connectRequestId = `tunnel-connect-${randomUUID2()}`;
|
|
6104
|
-
const gatewayAuth = this.resolveGatewayConnectAuth();
|
|
6105
|
-
this.opts.logger.info(
|
|
6106
|
-
`TunnelProxy: received connect.challenge (nonce=${challengeNonce}, connectReqId=${connectRequestId}, hasToken=${!!gatewayAuth?.token}, hasPassword=${!!gatewayAuth?.password}), sending connect request with device identity`
|
|
6107
|
-
);
|
|
6108
6251
|
const role = "operator";
|
|
6109
6252
|
const scopes = ["operator.admin"];
|
|
6253
|
+
const gatewayConnectAuth = this.buildGatewayConnectAuth(role);
|
|
6254
|
+
this.opts.logger.info(
|
|
6255
|
+
`TunnelProxy: received connect.challenge (nonce=${challengeNonce}, connectReqId=${connectRequestId}, hasToken=${!!gatewayConnectAuth.authToken}, hasPassword=${!!gatewayConnectAuth.authPassword}, hasDeviceToken=${!!gatewayConnectAuth.deviceToken}), sending connect request with device identity`
|
|
6256
|
+
);
|
|
6110
6257
|
const signedAtMs = Date.now();
|
|
6111
6258
|
const clientId = "gateway-client";
|
|
6112
6259
|
const clientMode = "backend";
|
|
@@ -6117,7 +6264,7 @@ var TunnelProxy = class _TunnelProxy {
|
|
|
6117
6264
|
role,
|
|
6118
6265
|
scopes,
|
|
6119
6266
|
signedAtMs,
|
|
6120
|
-
token:
|
|
6267
|
+
token: gatewayConnectAuth.authToken ?? null,
|
|
6121
6268
|
nonce: challengeNonce
|
|
6122
6269
|
});
|
|
6123
6270
|
const signature = signDevicePayload(
|
|
@@ -6139,7 +6286,7 @@ var TunnelProxy = class _TunnelProxy {
|
|
|
6139
6286
|
},
|
|
6140
6287
|
role,
|
|
6141
6288
|
scopes,
|
|
6142
|
-
...
|
|
6289
|
+
...gatewayConnectAuth.auth ? { auth: gatewayConnectAuth.auth } : {},
|
|
6143
6290
|
device: {
|
|
6144
6291
|
id: this.deviceIdentity.deviceId,
|
|
6145
6292
|
publicKey: publicKeyRawBase64UrlFromPem(
|
|
@@ -6155,6 +6302,11 @@ var TunnelProxy = class _TunnelProxy {
|
|
|
6155
6302
|
return;
|
|
6156
6303
|
}
|
|
6157
6304
|
if (frame.type === "res" && frame.ok === true && frame.payload?.type === "hello-ok") {
|
|
6305
|
+
this.storeIssuedDeviceToken({
|
|
6306
|
+
fallbackRole: "operator",
|
|
6307
|
+
fallbackScopes: ["operator.admin"],
|
|
6308
|
+
authInfo: frame.payload?.auth
|
|
6309
|
+
});
|
|
6158
6310
|
this.gatewayWsReady = true;
|
|
6159
6311
|
this.gatewayWsConnecting = false;
|
|
6160
6312
|
this.opts.logger.info(
|
|
@@ -6178,14 +6330,16 @@ var TunnelProxy = class _TunnelProxy {
|
|
|
6178
6330
|
ws.on("close", (code, reason) => {
|
|
6179
6331
|
const wasReady = this.gatewayWsReady;
|
|
6180
6332
|
const pendingCount = this.gatewayWsPending.length;
|
|
6333
|
+
const reasonText = reason.toString();
|
|
6181
6334
|
this.opts.logger.info(
|
|
6182
|
-
`TunnelProxy: RPC WS closed by gateway (code=${code}, reason=${
|
|
6335
|
+
`TunnelProxy: RPC WS closed by gateway (code=${code}, reason=${reasonText}, ready=${wasReady}, pending=${pendingCount}, activeWs=${this.wsConnections.size})`
|
|
6183
6336
|
);
|
|
6184
6337
|
if (this.gatewayWs === ws) {
|
|
6185
6338
|
this.gatewayWs = null;
|
|
6186
6339
|
this.gatewayWsReady = false;
|
|
6187
6340
|
}
|
|
6188
6341
|
this.gatewayWsConnecting = false;
|
|
6342
|
+
this.maybeClearStoredDeviceTokenOnMismatch(code, reasonText);
|
|
6189
6343
|
});
|
|
6190
6344
|
ws.on("error", (err) => {
|
|
6191
6345
|
this.opts.logger.warn(
|
|
@@ -6549,6 +6703,7 @@ function createTunnelService(opts) {
|
|
|
6549
6703
|
logger
|
|
6550
6704
|
});
|
|
6551
6705
|
proxy = new TunnelProxy({
|
|
6706
|
+
stateDir: baseStateDir,
|
|
6552
6707
|
gatewayBaseUrl: opts.gatewayBaseUrl,
|
|
6553
6708
|
gatewayAuthMode: opts.gatewayAuthMode,
|
|
6554
6709
|
gatewayToken: opts.gatewayToken,
|