Skip to content

Commit 357f2e8

Browse files
committed
2.3.0
Updated the behavior so that sensitive data is not logged unless debugMode is enabled.
1 parent b910fcc commit 357f2e8

9 files changed

Lines changed: 110 additions & 32 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Changelog
22

3+
## [2.3.0] - (2026.6.12)
4+
5+
### Added
6+
- Added an option to force communication over IPv4.
7+
- Added contact sensor state option for garage doors
8+
9+
### Changed
10+
- Updated the behavior so that sensitive data is not logged unless debugMode is enabled.
11+
12+
313
## [2.2.3] - (2026.5.8)
414

515
### Changed

package-lock.json

Lines changed: 17 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@homebridge-plugins/homebridge-tuya",
33
"displayName": "Homebridge Tuya",
4-
"version": "2.2.3",
4+
"version": "2.3.0",
55
"description": "Forked from 0x5e/homebridge-tuya-platform, with a focus on fixing bugs and adding new device support.",
66
"license": "MIT",
77
"repository": {
@@ -47,6 +47,7 @@
4747
"debounce": "^1.2.1",
4848
"jsonschema": "^1.4.1",
4949
"kelvin-to-rgb": "^1.0.2",
50+
"lodash": "^4.18.1",
5051
"lodash.isequal": "^4.5.0",
5152
"mqtt": "^4.2.6",
5253
"uuid": "^9.0.0"
@@ -56,6 +57,7 @@
5657
"@types/crypto-js": "^4.1.1",
5758
"@types/debounce": "^1.2.1",
5859
"@types/jest": "^29.1.2",
60+
"@types/lodash": "^4.17.24",
5961
"@types/lodash.isequal": "^4.5.6",
6062
"@types/node": "^22.0.0",
6163
"@types/uuid": "^8.3.4",

src/accessory/BaseAccessory.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import isEqual from 'lodash.isequal';
77
import { TuyaDeviceSchema, TuyaDeviceSchemaIntegerProperty, TuyaDeviceSchemaMode, TuyaDeviceStatus } from '../device/TuyaDevice';
88
import { TuyaPlatform } from '../platform';
99
import { limit, sanitizeName } from '../util/util';
10-
import { PrefixLogger } from '../util/Logger';
10+
import { logger, PrefixLogger } from '../util/Logger';
1111

1212
const MANUFACTURER = 'Tuya Inc.';
1313

@@ -31,7 +31,7 @@ class BaseAccessory {
3131
public deviceManager = this.platform.deviceManager!;
3232
public device = this.deviceManager.getDevice(this.accessory.context.deviceID)!;
3333
public log = new PrefixLogger(
34-
this.platform.log,
34+
logger(),
3535
this.device.name.length > 0 ? this.device.name : this.device.id,
3636
this.platform.options.debug && ((this.platform.options.debugLevel ?? '').length > 0
3737
? this.platform.options.debugLevel?.includes(this.device.id)

src/core/TuyaOpenAPI.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import dns from 'dns';
1111
// @ts-ignore
1212
import { version } from '../../package.json';
1313

14-
import Logger, { PrefixLogger } from '../util/Logger';
14+
import { ExLogger, logger, PrefixLogger } from '../util/Logger';
1515

1616
enum Endpoints {
1717
AMERICA = 'https://openapi.tuyaus.com',
@@ -88,17 +88,17 @@ export default class TuyaOpenAPI {
8888
public deviceArr: Array<object> = [];
8989

9090
public tokenInfo = { access_token: '', refresh_token: '', uid: '', expire: 0 };
91+
private log: ExLogger;
9192

9293
constructor(
9394
public endpoint: Endpoints | string,
9495
public accessId: string,
9596
public accessKey: string,
96-
public log: Logger = console,
9797
public lang = 'en',
9898
public debug = false,
9999
public forceIPv4 = false,
100100
) {
101-
this.log = new PrefixLogger(log, TuyaOpenAPI.name, debug);
101+
this.log = new PrefixLogger(logger(), TuyaOpenAPI.name, debug);
102102
}
103103

104104
static getDefaultEndpoint(countryCode: number) {

src/core/TuyaOpenMQ.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Crypto from 'crypto';
44
import CryptoJS from 'crypto-js';
55

66
import TuyaOpenAPI from './TuyaOpenAPI';
7-
import Logger, { PrefixLogger } from '../util/Logger';
7+
import { ExLogger, logger, PrefixLogger } from '../util/Logger';
88
import dns from 'dns';
99

1010
const GCM_TAG_LENGTH = 16;
@@ -34,14 +34,14 @@ export default class TuyaOpenMQ {
3434
public linkId = uuid_v4();
3535

3636
public timer?: NodeJS.Timeout;
37+
private log: ExLogger;
3738

3839
constructor(
3940
public api: TuyaOpenAPI,
40-
public log: Logger = new PrefixLogger(console, 'console', false),
4141
public debug = false,
4242
public forceIPv4 = api.forceIPv4,
4343
) {
44-
this.log = new PrefixLogger(log, TuyaOpenMQ.name, debug);
44+
this.log = new PrefixLogger(logger(), TuyaOpenMQ.name, debug);
4545
}
4646

4747
start() {

src/device/TuyaDeviceManager.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import EventEmitter from 'events';
22
import TuyaOpenAPI from '../core/TuyaOpenAPI';
33
import TuyaOpenMQ from '../core/TuyaOpenMQ';
4-
import { ExLogger, PrefixLogger } from '../util/Logger';
4+
import { ExLogger, logger, PrefixLogger } from '../util/Logger';
55
import TuyaDevice, {
66
TuyaDeviceSchema,
77
TuyaDeviceSchemaMode,
@@ -37,10 +37,9 @@ export default class TuyaDeviceManager extends EventEmitter {
3737
) {
3838
super();
3939

40-
const log = (this.api.log as PrefixLogger).log;
41-
this.log = new PrefixLogger(log, TuyaDeviceManager.name, debug);
40+
this.log = new PrefixLogger(logger(), TuyaDeviceManager.name, debug);
4241

43-
this.mq = new TuyaOpenMQ(api, log);
42+
this.mq = new TuyaOpenMQ(api);
4443
this.mq.addMessageListener(this.onMQTTMessage.bind(this));
4544
}
4645

src/platform.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import AccessoryFactory from './accessory/AccessoryFactory';
1414
import BaseAccessory from './accessory/BaseAccessory';
1515
import { sanitizeName } from './util/util';
1616
import TuyaOpenAPI, { LOGIN_ERROR_MESSAGES } from './core/TuyaOpenAPI';
17+
import { initLogger } from './util/Logger';
1718

1819

1920
/**
@@ -112,6 +113,7 @@ export class TuyaPlatform implements DynamicPlatformPlugin {
112113
public readonly config: PlatformConfig,
113114
public readonly api: API,
114115
) {
116+
initLogger(log);
115117

116118
if (!this.validate()) {
117119
return;
@@ -262,7 +264,7 @@ export class TuyaPlatform implements DynamicPlatformPlugin {
262264
let res;
263265
const { endpoint, accessId, accessKey, debug, debugLevel } = this.options;
264266
const debugMode = debug && ((debugLevel ?? '').length > 0 ? debugLevel?.includes('api') : true);
265-
const api = new TuyaOpenAPI(endpoint, accessId, accessKey, this.log, 'en', debugMode, this.options.forceIPv4);
267+
const api = new TuyaOpenAPI(endpoint, accessId, accessKey, 'en', debugMode, this.options.forceIPv4);
266268
const deviceManager = new TuyaCustomDeviceManager(api, debugMode);
267269

268270
this.log.info('Get token.');
@@ -355,7 +357,6 @@ export class TuyaPlatform implements DynamicPlatformPlugin {
355357
(endpoint && endpoint.length > 0) ? endpoint : TuyaOpenAPI.getDefaultEndpoint(countryCode),
356358
accessId,
357359
accessKey,
358-
this.log,
359360
'en',
360361
debugMode,
361362
this.options.forceIPv4);

src/util/Logger.ts

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22

3+
import { EOL } from 'os';
4+
import util from 'util';
5+
import cloneDeep from 'lodash/cloneDeep';
6+
37
export default interface Logger {
48
info(message?: any, ...args: any[]): void;
59
warn(message?: any, ...args: any[]): void;
@@ -14,41 +18,94 @@ function isExLogger(obj: any): obj is ExLogger {
1418
return typeof obj.success === 'function';
1519
}
1620

21+
let _logger: Logger;
22+
export const logger = () => _logger ?? console;
23+
export const initLogger = (logger: Logger) => _logger = logger;
24+
1725
export class PrefixLogger implements ExLogger {
1826
constructor(
1927
public log: Logger,
20-
public prefix: string,
28+
public prefix: string = '',
2129
public debugMode = false,
30+
private mask = !debugMode,
2231
) {
2332
this.debugMode = this.debugMode || process.argv.includes('-D') || process.argv.includes('--debug');
2433
}
2534

2635
debug(message?: any, ...args: any[]) {
36+
message = typeof message === 'string' ? this.masking(message) : this.maskingValue(message);
37+
args = args?.map(arg => typeof arg === 'string' ? this.masking(arg) : arg);
2738
if (this.debugMode) {
28-
this.log.info((this.prefix ? `[${this.prefix}] ` : '') + message, ...args);
39+
this.log.info(util.format(`[%s] ${typeof message === 'string' ? '%s' : '%O'}`, this.prefix, message), ...args);
2940
} else {
30-
this.log.debug((this.prefix ? `[${this.prefix}] ` : '') + message, ...args);
41+
this.log.debug(util.format(`[%s] ${typeof message === 'string' ? '%s' : '%O'}`, this.prefix, message), ...args);
3142
}
3243
}
3344

3445
info(message?: any, ...args: any[]) {
35-
this.log.info((this.prefix ? `[${this.prefix}] ` : '') + message, ...args);
46+
message = typeof message === 'string' ? this.masking(message) : this.maskingValue(message);
47+
args = args?.map(arg => typeof arg === 'string' ? this.masking(arg) : arg);
48+
this.log.info(util.format(`[%s] ${typeof message === 'string' ? '%s' : '%O'}`, this.prefix, message), ...args);
3649
}
3750

3851
warn(message?: any, ...args: any[]) {
39-
this.log.warn((this.prefix ? `[${this.prefix}] ` : '') + message, ...args);
52+
message = typeof message === 'string' ? this.masking(message) : this.maskingValue(message);
53+
args = args?.map(arg => typeof arg === 'string' ? this.masking(arg) : arg);
54+
this.log.warn(util.format(`[%s] ${typeof message === 'string' ? '%s' : '%O'}`, this.prefix, message), ...args);
4055
}
4156

4257
error(message?: any, ...args: any[]) {
43-
this.log.error((this.prefix ? `[${this.prefix}] ` : '') + message, ...args);
58+
message = typeof message === 'string' ? this.masking(message) : this.maskingValue(message);
59+
args = args?.map(arg => typeof arg === 'string' ? this.masking(arg) : arg);
60+
this.log.error(util.format(`[%s] ${typeof message === 'string' ? '%s' : '%O'}`, this.prefix, message), ...args);
4461
}
4562

4663
success(message?: any, ...args: any[]) {
64+
message = typeof message === 'string' ? this.masking(message) : this.maskingValue(message);
65+
args = args?.map(arg => typeof arg === 'string' ? this.masking(arg) : this.maskingValue(arg));
4766
if (isExLogger(this.log)) {
48-
this.log.success((this.prefix ? `[${this.prefix}] ` : '') + message, ...args);
67+
this.log.success(util.format(`[%s] ${typeof message === 'string' ? '%s' : '%O'}`, this.prefix, message), ...args);
4968
} else {
50-
this.log.info((this.prefix ? `[${this.prefix}] ` : '') + message, ...args);
69+
this.log.info(util.format(`[%s] ${typeof message === 'string' ? '%s' : '%O'}`, this.prefix, message), ...args);
5170
}
5271
}
5372

54-
}
73+
private masking(str: string) : string {
74+
if (!this.mask || typeof str !== 'string') {
75+
return str;
76+
}
77+
const regex_single = /'(password|token|access_?token|accessKey|tuyaKey|api_?key|secret)'\s*:\s*'[^']*'/gi;
78+
const regex_double = /"(password|token|access_?token|accessKey|tuyaKey|api_?key|secret)"\s*:\s*"[^"]*"/gi;
79+
const spilts = str.split(/\r\n|\n|\r/);
80+
if (!spilts.some(s => regex_single.test(s)) && !spilts.some(s => regex_double.test(s))) {
81+
return str;
82+
}
83+
const results = spilts
84+
.map(a => a.replace(regex_single, '\'$1\': \'********\''))
85+
.map(a => a.replace(regex_double, '"$1": "********"'));
86+
return results.join(EOL);
87+
}
88+
89+
private maskingValue(obj: any) {
90+
if (!this.mask) {
91+
return obj;
92+
}
93+
const cloneObj = cloneDeep(obj);
94+
const regex = /(password|token|access_?token|accessKey|tuyakey|api_?key|secret)/i;
95+
for (const key in cloneObj) {
96+
const value = cloneObj[key];
97+
if (typeof value === 'function') {
98+
continue;
99+
}
100+
if (typeof value === 'string') {
101+
if (!regex.test(key)) {
102+
continue;
103+
}
104+
cloneObj[key] = '*'.repeat(value.length);
105+
} else {
106+
cloneObj[key] = this.maskingValue(value);
107+
}
108+
}
109+
return cloneObj;
110+
}
111+
}

0 commit comments

Comments
 (0)