homebridge-blueair-plugin 1.1.0

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.
Files changed (44) hide show
  1. package/.eslintignore +3 -0
  2. package/.gitattributes +3 -0
  3. package/CHANGELOG.md +12 -0
  4. package/LICENSE +176 -0
  5. package/README.md +99 -0
  6. package/branding/Blueair.png +0 -0
  7. package/branding/Homebridge_x_Blueair.svg +82 -0
  8. package/dist/accessory.d.ts +111 -0
  9. package/dist/accessory.d.ts.map +1 -0
  10. package/dist/accessory.js +821 -0
  11. package/dist/accessory.js.map +1 -0
  12. package/dist/api/BlueAirAwsApi.d.ts +79 -0
  13. package/dist/api/BlueAirAwsApi.d.ts.map +1 -0
  14. package/dist/api/BlueAirAwsApi.js +216 -0
  15. package/dist/api/BlueAirAwsApi.js.map +1 -0
  16. package/dist/api/Consts.d.ts +36 -0
  17. package/dist/api/Consts.d.ts.map +1 -0
  18. package/dist/api/Consts.js +52 -0
  19. package/dist/api/Consts.js.map +1 -0
  20. package/dist/api/GigyaApi.d.ts +19 -0
  21. package/dist/api/GigyaApi.d.ts.map +1 -0
  22. package/dist/api/GigyaApi.js +79 -0
  23. package/dist/api/GigyaApi.js.map +1 -0
  24. package/dist/constants.d.ts +40 -0
  25. package/dist/constants.d.ts.map +1 -0
  26. package/dist/constants.js +146 -0
  27. package/dist/constants.js.map +1 -0
  28. package/dist/device.d.ts +49 -0
  29. package/dist/device.d.ts.map +1 -0
  30. package/dist/device.js +136 -0
  31. package/dist/device.js.map +1 -0
  32. package/dist/index.d.ts +2 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +11 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/platform.d.ts +35 -0
  37. package/dist/platform.d.ts.map +1 -0
  38. package/dist/platform.js +277 -0
  39. package/dist/platform.js.map +1 -0
  40. package/dist/utils.d.ts +96 -0
  41. package/dist/utils.d.ts.map +1 -0
  42. package/dist/utils.js +98 -0
  43. package/dist/utils.js.map +1 -0
  44. package/package.json +54 -0
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ /**
3
+ * Constants for BlueAir accessory control
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DEBOUNCE_DELAY_MS = exports.AQI_THRESHOLDS = exports.NL_HOMEKIT_STEP = exports.NL_LEVELS = exports.FAN_SPEED_HOMEKIT_VALUES = exports.FAN_SPEED_HOMEKIT_STEP = exports.FAN_SPEED_LEVELS = exports.HUMIDITY_MAX = exports.HUMIDITY_MIN = exports.AQI = void 0;
7
+ exports.fanSpeedHomeKitToDevice = fanSpeedHomeKitToDevice;
8
+ exports.fanSpeedDeviceToHomeKit = fanSpeedDeviceToHomeKit;
9
+ exports.mapToDeviceSpeed = mapToDeviceSpeed;
10
+ exports.nlDeviceToHomeKit = nlDeviceToHomeKit;
11
+ exports.nlHomeKitToDevice = nlHomeKitToDevice;
12
+ exports.nlDeviceToName = nlDeviceToName;
13
+ exports.AQI = {
14
+ PM2_5: {
15
+ AQI_LO: [0, 51, 101, 151, 201, 301],
16
+ AQI_HI: [50, 100, 150, 200, 300, 500],
17
+ CONC_LO: [0.0, 9.1, 35.5, 55.5, 125.5, 225.5],
18
+ CONC_HI: [9.0, 35.4, 55.4, 125.4, 225.4, 325.4],
19
+ },
20
+ PM10: {
21
+ AQI_LO: [0, 51, 101, 151, 201, 301],
22
+ AQI_HI: [50, 100, 150, 200, 300, 500],
23
+ CONC_LO: [0, 55, 155, 255, 355, 425],
24
+ CONC_HI: [54, 154, 254, 354, 424, 604],
25
+ },
26
+ VOC: {
27
+ AQI_LO: [0, 51, 101, 151, 201, 301],
28
+ AQI_HI: [50, 100, 150, 200, 300, 500],
29
+ CONC_LO: [0, 221, 661, 1431, 2201, 3301],
30
+ CONC_HI: [220, 660, 1430, 2200, 3300, 5500],
31
+ },
32
+ };
33
+ // Humidity control range
34
+ exports.HUMIDITY_MIN = 30;
35
+ exports.HUMIDITY_MAX = 80;
36
+ // Fan speed levels: 0 (sleep/night), 11 (low), 37 (medium), 64 (high)
37
+ exports.FAN_SPEED_LEVELS = [0, 11, 37, 64];
38
+ // HomeKit fan speed mapping - use 5 discrete positions (0, 25, 50, 75, 100)
39
+ // 0% = Off/Standby, 25% = Sleep/Night, 50% = Low, 75% = Medium, 100% = High
40
+ exports.FAN_SPEED_HOMEKIT_STEP = 25;
41
+ exports.FAN_SPEED_HOMEKIT_VALUES = [0, 25, 50, 75, 100];
42
+ // Map HomeKit percentage to device speed
43
+ // Returns { speed: number, isNightMode: boolean, isStandby: boolean }
44
+ function fanSpeedHomeKitToDevice(hkSpeed) {
45
+ if (hkSpeed <= 12) {
46
+ // 0% = Standby (device off)
47
+ return { speed: 0, isNightMode: false, isStandby: true };
48
+ }
49
+ if (hkSpeed <= 37) {
50
+ // 25% = Sleep/Night mode (device on, night mode enabled)
51
+ return { speed: 0, isNightMode: true, isStandby: false };
52
+ }
53
+ if (hkSpeed <= 62) {
54
+ // 50% = Low
55
+ return { speed: 11, isNightMode: false, isStandby: false };
56
+ }
57
+ if (hkSpeed <= 87) {
58
+ // 75% = Medium
59
+ return { speed: 37, isNightMode: false, isStandby: false };
60
+ }
61
+ // 100% = High
62
+ return { speed: 64, isNightMode: false, isStandby: false };
63
+ }
64
+ // Map device state to HomeKit percentage
65
+ function fanSpeedDeviceToHomeKit(deviceSpeed, isNightMode, isStandby) {
66
+ if (isStandby) {
67
+ return 0;
68
+ } // Off → 0%
69
+ if (isNightMode) {
70
+ return 25;
71
+ } // Night mode → 25%
72
+ if (deviceSpeed <= 5) {
73
+ return 25;
74
+ } // Speed 0 without night mode → treat as sleep
75
+ if (deviceSpeed <= 24) {
76
+ return 50;
77
+ } // Low → 50%
78
+ if (deviceSpeed <= 50) {
79
+ return 75;
80
+ } // Medium → 75%
81
+ return 100; // High → 100%
82
+ }
83
+ // Legacy function for backward compatibility
84
+ function mapToDeviceSpeed(hkSpeed) {
85
+ return fanSpeedHomeKitToDevice(hkSpeed).speed;
86
+ }
87
+ // Night light levels: device uses 0-3, HomeKit uses discrete 0/33/66/100%
88
+ exports.NL_LEVELS = {
89
+ OFF: 0,
90
+ WARM: 1,
91
+ NORMAL: 2,
92
+ BRIGHT: 3,
93
+ };
94
+ // HomeKit night light step - 3 brightness levels + off
95
+ exports.NL_HOMEKIT_STEP = 33;
96
+ // Convert device night light value (0-3) to HomeKit percentage (0, 33, 66, 100)
97
+ function nlDeviceToHomeKit(deviceValue) {
98
+ switch (deviceValue) {
99
+ case 0:
100
+ return 0;
101
+ case 1:
102
+ return 33; // warm
103
+ case 2:
104
+ return 66; // normal
105
+ case 3:
106
+ return 100; // bright
107
+ default:
108
+ return 0;
109
+ }
110
+ }
111
+ // Convert HomeKit percentage to device night light value (0-3)
112
+ function nlHomeKitToDevice(hkValue) {
113
+ if (hkValue <= 16) {
114
+ return exports.NL_LEVELS.OFF;
115
+ }
116
+ if (hkValue <= 49) {
117
+ return exports.NL_LEVELS.WARM;
118
+ }
119
+ if (hkValue <= 83) {
120
+ return exports.NL_LEVELS.NORMAL;
121
+ }
122
+ return exports.NL_LEVELS.BRIGHT;
123
+ }
124
+ // Get human-readable name for night light level
125
+ function nlDeviceToName(deviceValue) {
126
+ switch (deviceValue) {
127
+ case 1:
128
+ return "Warm";
129
+ case 2:
130
+ return "Normal";
131
+ case 3:
132
+ return "Bright";
133
+ default:
134
+ return "Off";
135
+ }
136
+ }
137
+ // AQI thresholds for HomeKit AirQuality characteristic
138
+ exports.AQI_THRESHOLDS = {
139
+ EXCELLENT: 50,
140
+ GOOD: 100,
141
+ FAIR: 150,
142
+ INFERIOR: 200,
143
+ };
144
+ // Default debounce delay for HomeKit slider interactions (ms)
145
+ exports.DEBOUNCE_DELAY_MS = 500;
146
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * BlueAirDevice - State management model for BlueAir devices
3
+ *
4
+ * This class acts as a bridge between the platform (API) and accessories (HomeKit).
5
+ * It holds device state, calculates AQI, and emits events for state changes.
6
+ */
7
+ import EventEmitter from "events";
8
+ import { BlueAirDeviceSensorData, BlueAirDeviceState, BlueAirDeviceStatus, FullBlueAirDeviceState } from "./api/BlueAirAwsApi";
9
+ export type BlueAirSensorDataWithAqi = BlueAirDeviceSensorData & {
10
+ aqi?: number;
11
+ };
12
+ interface BlueAirDeviceEvents {
13
+ stateUpdated: (changedStates: Partial<FullBlueAirDeviceState>) => void;
14
+ update: (newState: BlueAirDeviceStatus) => void;
15
+ setState: (data: {
16
+ id: string;
17
+ name: string;
18
+ attribute: string;
19
+ value: number | boolean;
20
+ }) => void;
21
+ setStateDone: (success: boolean) => void;
22
+ }
23
+ export interface BlueAirDevice {
24
+ on<K extends keyof BlueAirDeviceEvents>(event: K, listener: BlueAirDeviceEvents[K]): this;
25
+ emit<K extends keyof BlueAirDeviceEvents>(event: K, ...args: Parameters<BlueAirDeviceEvents[K]>): boolean;
26
+ once<K extends keyof BlueAirDeviceEvents>(event: K, listener: BlueAirDeviceEvents[K]): this;
27
+ }
28
+ export declare class BlueAirDevice extends EventEmitter {
29
+ state: BlueAirDeviceState;
30
+ sensorData: BlueAirSensorDataWithAqi;
31
+ readonly id: string;
32
+ readonly name: string;
33
+ private mutex;
34
+ private currentChanges;
35
+ constructor(device: BlueAirDeviceStatus);
36
+ private hasChanges;
37
+ private notifyStateUpdate;
38
+ /**
39
+ * Request a state change on the device.
40
+ * Emits "setState" event for the platform to handle the API call.
41
+ * Waits for "setStateDone" before updating local state.
42
+ */
43
+ setState(attribute: string, value: number | boolean): Promise<void>;
44
+ private updateState;
45
+ private calculateAqi;
46
+ private calculateAqiForSensor;
47
+ }
48
+ export {};
49
+ //# sourceMappingURL=device.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device.d.ts","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,YAAY,MAAM,QAAQ,CAAC;AAClC,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACvB,MAAM,qBAAqB,CAAC;AAI7B,MAAM,MAAM,wBAAwB,GAAG,uBAAuB,GAAG;IAC/D,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAOF,UAAU,mBAAmB;IAC3B,YAAY,EAAE,CAAC,aAAa,EAAE,OAAO,CAAC,sBAAsB,CAAC,KAAK,IAAI,CAAC;IACvE,MAAM,EAAE,CAAC,QAAQ,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAChD,QAAQ,EAAE,CAAC,IAAI,EAAE;QACf,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;KACzB,KAAK,IAAI,CAAC;IACX,YAAY,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;CAC1C;AAGD,MAAM,WAAW,aAAa;IAC5B,EAAE,CAAC,CAAC,SAAS,MAAM,mBAAmB,EACpC,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAC/B,IAAI,CAAC;IACR,IAAI,CAAC,CAAC,SAAS,MAAM,mBAAmB,EACtC,KAAK,EAAE,CAAC,EACR,GAAG,IAAI,EAAE,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,GAC1C,OAAO,CAAC;IACX,IAAI,CAAC,CAAC,SAAS,MAAM,mBAAmB,EACtC,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAC/B,IAAI,CAAC;CACT;AAGD,qBAAa,aAAc,SAAQ,YAAY;IACtC,KAAK,EAAE,kBAAkB,CAAC;IAC1B,UAAU,EAAE,wBAAwB,CAAC;IAE5C,SAAgB,EAAE,EAAE,MAAM,CAAC;IAC3B,SAAgB,IAAI,EAAE,MAAM,CAAC;IAE7B,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,cAAc,CAAiB;gBAE3B,MAAM,EAAE,mBAAmB;IAqBvC,OAAO,CAAC,UAAU;YAOJ,iBAAiB;IAgC/B;;;;OAIG;IACU,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;YA0BlD,WAAW;IAoBzB,OAAO,CAAC,YAAY;IAoBpB,OAAO,CAAC,qBAAqB;CAc9B"}
package/dist/device.js ADDED
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ /**
3
+ * BlueAirDevice - State management model for BlueAir devices
4
+ *
5
+ * This class acts as a bridge between the platform (API) and accessories (HomeKit).
6
+ * It holds device state, calculates AQI, and emits events for state changes.
7
+ */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.BlueAirDevice = void 0;
13
+ const events_1 = __importDefault(require("events"));
14
+ const async_mutex_1 = require("async-mutex");
15
+ const constants_1 = require("./constants");
16
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
17
+ class BlueAirDevice extends events_1.default {
18
+ constructor(device) {
19
+ super();
20
+ this.id = device.id;
21
+ this.name = device.name;
22
+ this.state = device.state;
23
+ this.sensorData = {
24
+ ...device.sensorData,
25
+ aqi: undefined,
26
+ };
27
+ this.sensorData.aqi = this.calculateAqi();
28
+ this.mutex = new async_mutex_1.Mutex();
29
+ this.currentChanges = {
30
+ state: {},
31
+ sensorData: {},
32
+ };
33
+ this.on("update", this.updateState.bind(this));
34
+ }
35
+ hasChanges(changes) {
36
+ return (Object.keys(changes.state).length > 0 ||
37
+ Object.keys(changes.sensorData).length > 0);
38
+ }
39
+ async notifyStateUpdate(newState, newSensorData) {
40
+ this.currentChanges = {
41
+ state: {
42
+ ...this.currentChanges.state,
43
+ ...newState,
44
+ },
45
+ sensorData: {
46
+ ...this.currentChanges.sensorData,
47
+ ...newSensorData,
48
+ },
49
+ };
50
+ const release = await this.mutex.acquire();
51
+ const changesToApply = this.currentChanges;
52
+ this.currentChanges = { state: {}, sensorData: {} };
53
+ if (this.hasChanges(changesToApply)) {
54
+ this.state = { ...this.state, ...changesToApply.state };
55
+ this.sensorData = { ...this.sensorData, ...changesToApply.sensorData };
56
+ this.emit("stateUpdated", {
57
+ ...changesToApply.state,
58
+ ...changesToApply.sensorData,
59
+ });
60
+ }
61
+ release();
62
+ }
63
+ /**
64
+ * Request a state change on the device.
65
+ * Emits "setState" event for the platform to handle the API call.
66
+ * Waits for "setStateDone" before updating local state.
67
+ */
68
+ async setState(attribute, value) {
69
+ // Skip if value is unchanged
70
+ if (attribute in this.state && this.state[attribute] === value) {
71
+ return;
72
+ }
73
+ this.emit("setState", { id: this.id, name: this.name, attribute, value });
74
+ const release = await this.mutex.acquire();
75
+ return new Promise((resolve) => {
76
+ this.once("setStateDone", async (success) => {
77
+ release();
78
+ if (success) {
79
+ const newState = { [attribute]: value };
80
+ // Night mode turns off display brightness
81
+ if (attribute === "nightmode" && value === true) {
82
+ newState["brightness"] = 0;
83
+ }
84
+ await this.notifyStateUpdate(newState);
85
+ }
86
+ resolve();
87
+ });
88
+ });
89
+ }
90
+ async updateState(newState) {
91
+ const changedState = {};
92
+ const changedSensorData = {};
93
+ for (const [k, v] of Object.entries(newState.state)) {
94
+ if (this.state[k] !== v) {
95
+ changedState[k] = v;
96
+ }
97
+ }
98
+ for (const [k, v] of Object.entries(newState.sensorData)) {
99
+ if (this.sensorData[k] !== v) {
100
+ changedSensorData[k] = v;
101
+ if (k === "pm25" || k === "pm10" || k === "voc") {
102
+ changedSensorData.aqi = this.calculateAqi();
103
+ }
104
+ }
105
+ }
106
+ await this.notifyStateUpdate(changedState, changedSensorData);
107
+ }
108
+ calculateAqi() {
109
+ if (this.sensorData.pm2_5 === undefined &&
110
+ this.sensorData.pm10 === undefined &&
111
+ this.sensorData.voc === undefined) {
112
+ return undefined;
113
+ }
114
+ const pm2_5 = Math.round((this.sensorData.pm2_5 || 0) * 10) / 10;
115
+ const pm10 = this.sensorData.pm10 || 0;
116
+ const voc = this.sensorData.voc || 0;
117
+ const aqi_pm2_5 = this.calculateAqiForSensor(pm2_5, "PM2_5");
118
+ const aqi_pm10 = this.calculateAqiForSensor(pm10, "PM10");
119
+ const aqi_voc = this.calculateAqiForSensor(voc, "VOC");
120
+ return Math.max(aqi_pm2_5, aqi_pm10, aqi_voc);
121
+ }
122
+ calculateAqiForSensor(value, sensor) {
123
+ const levels = constants_1.AQI[sensor];
124
+ for (let i = 0; i < levels.AQI_LO.length; i++) {
125
+ if (value >= levels.CONC_LO[i] && value <= levels.CONC_HI[i]) {
126
+ return Math.round(((levels.AQI_HI[i] - levels.AQI_LO[i]) /
127
+ (levels.CONC_HI[i] - levels.CONC_LO[i])) *
128
+ (value - levels.CONC_LO[i]) +
129
+ levels.AQI_LO[i]);
130
+ }
131
+ }
132
+ return 0;
133
+ }
134
+ }
135
+ exports.BlueAirDevice = BlueAirDevice;
136
+ //# sourceMappingURL=device.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device.js","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("./utils");
4
+ const platform_1 = require("./platform");
5
+ /**
6
+ * This method registers the platform with Homebridge
7
+ */
8
+ module.exports = (api) => {
9
+ api.registerPlatform(utils_1.PLATFORM_NAME, platform_1.BlueAirPlatform);
10
+ };
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAEA,mCAAwC;AACxC,yCAA6C;AAE7C;;GAEG;AACH,MAAM,CAAC,OAAO,GAAG,CAAC,GAAQ,EAAE,EAAE;IAC5B,GAAG,CAAC,gBAAgB,CAAC,qBAAa,EAAE,0BAAe,CAAC,CAAC;AACvD,CAAC,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service, Characteristic } from "homebridge";
2
+ import { BlueAirDeviceStatus } from "./api/BlueAirAwsApi";
3
+ import EventEmitter from "events";
4
+ export declare class BlueAirPlatform extends EventEmitter implements DynamicPlatformPlugin {
5
+ readonly log: Logger;
6
+ readonly config: PlatformConfig;
7
+ readonly api: API;
8
+ readonly Service: typeof Service;
9
+ readonly Characteristic: typeof Characteristic;
10
+ readonly accessories: PlatformAccessory[];
11
+ private readonly platformConfig;
12
+ private readonly blueAirApi;
13
+ private existingUuids;
14
+ private devices;
15
+ private polling;
16
+ constructor(log: Logger, config: PlatformConfig, api: API);
17
+ configureAccessory(accessory: PlatformAccessory): void;
18
+ private retryCount;
19
+ private readonly MAX_RETRY_COUNT;
20
+ getValidDevicesStatus(): Promise<void>;
21
+ getInitialDeviceStates(retryCount?: number, maxRetries?: number): Promise<void>;
22
+ addDevice(device: BlueAirDeviceStatus): Promise<void>;
23
+ /**
24
+ * Get available devices from API for auto-discovery
25
+ * Used by the configuration UI to populate available devices
26
+ */
27
+ getAvailableDevices(): Promise<Array<{
28
+ uuid: string;
29
+ name: string;
30
+ type: string;
31
+ mac: string;
32
+ }>>;
33
+ private isHumidifierDevice;
34
+ }
35
+ //# sourceMappingURL=platform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,GAAG,EACH,qBAAqB,EACrB,MAAM,EACN,iBAAiB,EACjB,cAAc,EACd,OAAO,EACP,cAAc,EACf,MAAM,YAAY,CAAC;AAIpB,OAAsB,EACpB,mBAAmB,EAEpB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,YAAY,MAAM,QAAQ,CAAC;AAElC,qBAAa,eACX,SAAQ,YACR,YAAW,qBAAqB;aAiBd,GAAG,EAAE,MAAM;aACX,MAAM,EAAE,cAAc;aACtB,GAAG,EAAE,GAAG;IAjB1B,SAAgB,OAAO,EAAE,OAAO,OAAO,CAAC;IACxC,SAAgB,cAAc,EAAE,OAAO,cAAc,CAAC;IAGtD,SAAgB,WAAW,EAAE,iBAAiB,EAAE,CAAM;IAEtD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAgB;IAE3C,OAAO,CAAC,aAAa,CAAgB;IAErC,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,OAAO,CAA+B;gBAG5B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,GAAG;IAqC1B,kBAAkB,CAAC,SAAS,EAAE,iBAAiB;IAK/C,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAK;IAE/B,qBAAqB;IAyErB,sBAAsB,CAAC,UAAU,SAAI,EAAE,UAAU,SAAI;IAmFrD,SAAS,CAAC,MAAM,EAAE,mBAAmB;IA4E3C;;;OAGG;IACG,mBAAmB,IAAI,OAAO,CAClC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CACjE;IAgBD,OAAO,CAAC,kBAAkB;CA6B3B"}