dbus-sdk
Version:
A Node.js SDK for interacting with DBus, enabling seamless service calling and exposure with TypeScript support
391 lines (390 loc) • 22.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DBusSignedValue = void 0;
const Signature_1 = require("./Signature");
const Errors_1 = require("./Errors");
/**
* A class representing a DBus signed value with a specific signature and associated data.
* This class wraps a value with its corresponding DBus type signature, enabling proper
* encoding and decoding of DBus messages. It supports both basic types (e.g., integers, strings)
* and container types (e.g., arrays, structs, dictionaries) as defined by the DBus specification.
*/
class DBusSignedValue {
/**
* Static method to parse a DBus signature and value into an array of DBusSignedValue objects.
* Distinguishes between independent parameter sequences (e.g., 'si' for string and integer)
* and structs (e.g., '(si)' for a struct containing string and integer).
* This method is useful for creating properly typed DBus values from raw data.
*
* @param signature - The DBus signature string (e.g., 's', 'si', '(si)', 'as') defining the type structure.
* @param value - The value or values corresponding to the signature, can be raw data or already DBusSignedValue.
* @returns An array of DBusSignedValue objects representing the parsed signature and value.
* @throws {SignatureError} If the value structure does not match the signature or is invalid.
*/
static parse(signature, value) {
// Parse the signature string into an array of DataType objects
const dataTypes = Signature_1.Signature.parseSignature(signature);
// Check if the signature represents a struct (starts with '(' and ends with ')')
const isStruct = signature.trim().startsWith('(') && signature.trim().endsWith(')');
if (dataTypes.length > 1 && !isStruct) {
// If the signature has multiple types and is not a struct, treat it as an independent parameter sequence (e.g., 'si')
let values;
if (Array.isArray(value)) {
values = value; // Use array directly if provided
}
else if (typeof value === 'object' && value !== null) {
values = Object.values(value); // Convert object to array of values if provided as object
}
else {
throw new Errors_1.SignatureError(`Expected array or object for multi-type signature, got: ${typeof value}`);
}
if (values.length !== dataTypes.length) {
throw new Errors_1.SignatureError(`Value length (${values.length}) does not match signature length (${dataTypes.length})`);
}
// Create independent DBusSignedValue objects for each type and corresponding value
// Check if the value is already a DBusSignedValue instance
return dataTypes.map((dataType, index) => values[index] instanceof DBusSignedValue ? values[index] : new DBusSignedValue(dataType, values[index]));
}
else {
// For single types, structs, or other composite types, return a single DBusSignedValue object
// If value is already a DBusSignedValue instance and matches the signature, return it directly
if (value instanceof DBusSignedValue && dataTypes.length === 1 && value.$signature === dataTypes[0].type) {
return [value];
}
return [new DBusSignedValue(dataTypes, value)];
}
}
/**
* Static method to convert an array of DBusSignedValue objects back to plain JavaScript objects.
* Recursively processes nested structures (arrays, structs, dictionaries) and removes DBus
* signature metadata to return raw values suitable for general use.
*
* @param values - An array of DBusSignedValue objects to convert.
* @returns An array of plain JavaScript values representing the data without DBus signatures.
*/
static toJSON(values) {
return values.map((value) => DBusSignedValue.convertToPlain(value));
}
/**
* Private static method to recursively convert a single DBusSignedValue object to a plain JavaScript value.
* Handles different DBus types (basic, array, dictionary, struct, variant) and unwraps nested structures.
*
* @param value - The DBusSignedValue object to convert.
* @returns The plain JavaScript value extracted from the DBusSignedValue.
*/
static convertToPlain(value) {
// If the value is a basic type (not a composite type), return the raw value
if (!Array.isArray(value.$value) && !(value.$value instanceof DBusSignedValue)) {
return value.$value;
}
// Handle different signature types for container structures
switch (value.$signature) {
case 'a': // Array
const arrayValues = value.$value.map(item => DBusSignedValue.convertToPlain(item));
// Check if the array represents a dictionary array (e.g., a{sv})
if (arrayValues.length > 0 && typeof arrayValues[0] === 'object' && !Array.isArray(arrayValues[0])) {
// Merge dictionary entries into a single object
return arrayValues.reduce((acc, curr) => ({ ...acc, ...curr }), {});
}
// Check if the array represents a byte array (ay)
if (arrayValues.length > 0 && Array.isArray(value.$value) && value.$value.length > 0 && value.$value[0].$signature === 'y') {
// Convert to Buffer for byte array
return Buffer.from(arrayValues);
}
// Handle empty dictionary array case
if (!arrayValues.length && value.$arrayItemSignature === '{') {
return {};
}
return arrayValues;
case '{': // Dictionary (key-value pair)
const [key, val] = value.$value;
return {
[DBusSignedValue.convertToPlain(key)]: DBusSignedValue.convertToPlain(val)
};
case '(': // Struct
return value.$value.map(item => DBusSignedValue.convertToPlain(item));
case 'v': // Variant
return DBusSignedValue.convertToPlain(value.$value);
default:
// For nested structures or unsupported types, recursively process if it's a DBusSignedValue
if (value.$value instanceof DBusSignedValue) {
return DBusSignedValue.convertToPlain(value.$value);
}
else if (Array.isArray(value.$value)) {
return value.$value.map((item) => item instanceof DBusSignedValue ? DBusSignedValue.convertToPlain(item) : item);
}
return value.$value;
}
}
/**
* Constructor for creating a DBusSignedValue object from a signature and value.
* Processes the signature (string, DataType, or array of DataType) and associates it with the provided value,
* handling basic types, structs, arrays, dictionaries, and variants appropriately.
*
* @param signature - The DBus signature, as a string (e.g., 's'), a single DataType, or an array of DataType for structs.
* @param value - The value associated with the signature, can be raw data, DBusSignedValue, or array of values.
* @throws {SignatureError} If the value structure does not match the signature or if the type is unsupported.
*/
constructor(signature, value) {
// Handle signature input: parse string to DataType array, or use directly if already DataType or array
const dataTypes = typeof signature === 'string'
? signature.length > 1 ? Signature_1.Signature.parseSignature(signature) : [{ type: signature }]
: Array.isArray(signature)
? signature
: [signature];
// If the signature contains multiple types, infer it as a struct or sequence
if (dataTypes.length > 1) {
this.$signature = '('; // Struct signature
let structValues;
if (Array.isArray(value)) {
structValues = value; // Use array directly if provided
}
else if (typeof value === 'object' && value !== null) {
structValues = Object.values(value); // Convert object to array of values if provided as object
}
else {
throw new Errors_1.SignatureError(`Expected array or object for struct signature "()", got: ${typeof value}`);
}
if (structValues.length !== dataTypes.length) {
throw new Errors_1.SignatureError(`Struct value length (${structValues.length}) does not match signature child length (${dataTypes.length})`);
}
// Check if each value is already a DBusSignedValue instance
this.$value = structValues.map((item, index) => {
if (item instanceof DBusSignedValue) {
// If the item is a DBusSignedValue and the corresponding type is 'v', wrap it in a variant structure
if (dataTypes[index].type === 'v') {
return new DBusSignedValue('v', item);
}
return item;
}
return new DBusSignedValue(dataTypes[index], item);
});
}
else {
const dataType = dataTypes[0];
this.$signature = dataType.type; // Extract the type as the signature for single type
// Set $arrayItemSignature for array type 'a'
if (dataType.type === 'a' && dataType.child && dataType.child.length > 0) {
this.$arrayItemSignature = dataType.child[0].type;
}
// Process value based on type
if (dataType.type === 'v') {
// Special handling for variant type
if (value instanceof DBusSignedValue) {
// If value is already a DBusSignedValue instance, use it directly (it will be wrapped in variant structure by caller if needed)
this.$value = value;
}
else {
// Otherwise, infer signature from value type
const inferredType = this.inferType(value);
const inferredDataType = Signature_1.Signature.parseSignature(inferredType)[0];
this.$value = new DBusSignedValue(inferredDataType, value);
}
}
else if (dataType.child && dataType.child.length > 0) {
// Handle composite types: array, dictionary, struct
switch (dataType.type) {
case 'a': // Array
// Check if value is an array or TypedArray-like object
const isArrayLike = Array.isArray(value) || (typeof value === 'object' && value !== null && 'length' in value && typeof value.length === 'number' && value.length >= 0);
if (!isArrayLike) {
// For dictionary arrays a{...}, support object input
if (dataType.child[0].type === '{' && typeof value === 'object' && value !== null) {
const entries = Object.entries(value);
if (entries.length === 0) {
// If the input is an empty object, create an empty array with the correct signature structure
this.$value = [];
}
else {
this.$value = entries.map(([key, val]) => {
// Use parsed DataType objects directly to avoid re-parsing
const keyDataType = dataType.child[0].child?.[0];
const valueDataType = dataType.child[0].child?.[1];
if (!keyDataType || !valueDataType) {
throw new Errors_1.SignatureError('Invalid dictionary child types');
}
const keyObj = new DBusSignedValue(keyDataType, key);
const valObj = new DBusSignedValue(valueDataType, val);
// Create a DBusSignedValue for a single dictionary entry {...}
return new DBusSignedValue(dataType.child[0], [keyObj, valObj]);
});
}
break;
}
else if (dataType.child[0].type === 'y' && Buffer.isBuffer(value)) {
// For 'ay' type, convert Buffer to array if provided
const arrayValue = Array.from(value);
this.$value = arrayValue.map(byte => new DBusSignedValue('y', byte));
break;
}
throw new Errors_1.SignatureError(`Expected array for signature "a", got: ${typeof value}. Non-dictionary array types do not support object input except for Buffer with ay.`);
}
// Handle Array or TypedArray for array type
if (dataType.child[0].type === 'v') {
// If child type is variant, check if elements are DBusSignedValue instances
this.$value = Array.from(value).map((item) => item instanceof DBusSignedValue
? new DBusSignedValue('v', item) // Wrap in variant structure
: new DBusSignedValue(dataType.child[0], item));
}
else {
// For non-variant child types, check if elements are DBusSignedValue instances
this.$value = Array.from(value).map((item) => item instanceof DBusSignedValue ? item : new DBusSignedValue(dataType.child[0], item));
}
break;
case '{': // Dictionary (single key-value pair)
if (dataType.child.length !== 2) {
throw new Errors_1.SignatureError('Dictionary signature "{}" must have exactly 2 child types');
}
if (Array.isArray(value)) {
// Support array format for key-value pair
if (value.length !== 2) {
throw new Errors_1.SignatureError(`Expected key-value pair array of length 2 for dictionary signature "{}", got length: ${value.length}`);
}
// Check if elements in value are already DBusSignedValue instances
this.$value = value.map((item, index) => item instanceof DBusSignedValue
? item
: new DBusSignedValue(dataType.child[index], item));
}
else if (typeof value === 'object' && value !== null) {
// For single dictionary entry, take the first key-value pair
const entries = Object.entries(value);
if (entries.length !== 1) {
throw new Errors_1.SignatureError(`Expected object with exactly one key-value pair for dictionary signature "{}", got ${entries.length} pairs`);
}
const [key, val] = entries[0];
this.$value = [
key instanceof DBusSignedValue ? key : new DBusSignedValue(dataType.child[0], key),
val instanceof DBusSignedValue ? val : new DBusSignedValue(dataType.child[1], val)
];
}
else {
throw new Errors_1.SignatureError(`Expected array or object with one key-value pair for dictionary signature "{}", got: ${typeof value}`);
}
break;
case '(': // Struct
let structValues;
if (Array.isArray(value)) {
structValues = value; // Use array directly if provided
}
else if (typeof value === 'object' && value !== null) {
// If input is an object, convert to array based on value order
structValues = Object.values(value);
}
else {
throw new Errors_1.SignatureError(`Expected array or object for struct signature "()", got: ${typeof value}`);
}
if (structValues.length !== dataType.child.length) {
throw new Errors_1.SignatureError(`Struct value length (${structValues.length}) does not match signature child length (${dataType.child.length})`);
}
// Check if each value is already a DBusSignedValue instance
this.$value = structValues.map((item, index) => {
if (item instanceof DBusSignedValue) {
// If the item is a DBusSignedValue and the corresponding type is 'v', wrap it in a variant structure
if (dataType.child[index].type === 'v') {
return new DBusSignedValue('v', item);
}
return item;
}
return new DBusSignedValue(dataType.child[index], item);
});
break;
default:
throw new Errors_1.SignatureError(`Unsupported composite type: "${dataType.type}"`);
}
}
else {
// Basic type, store value directly without further wrapping
this.$value = value;
}
}
}
/**
* Private method to infer the DBus signature type from a value for variant type 'v'.
* Analyzes the type and structure of the value to determine an appropriate DBus signature.
* Handles basic types (strings, numbers), arrays, buffers, and objects for dictionary inference.
*
* @param value - The value to infer the type from, can be any JavaScript value.
* @returns The inferred DBus signature string (e.g., 's' for string, 'ai' for integer array).
*/
inferType(value) {
// If value is already a DBusSignedValue instance, use its signature
if (value instanceof DBusSignedValue) {
return value.$signature;
}
// Infer signature based on value type and structure
if (typeof value === 'string') {
return 's'; // String
}
else if (typeof value === 'number') {
// Check if integer or floating-point
return Number.isInteger(value) ? 'i' : 'd'; // Integer or double
}
else if (typeof value === 'boolean') {
return 'b'; // Boolean
}
else if (typeof value === 'bigint') {
return 'x'; // 64-bit integer
}
else if (Buffer.isBuffer(value)) {
return 'ay'; // Byte array
}
else if (value instanceof Uint8Array) {
return 'ay'; // Byte array
}
else if (value instanceof Int8Array) {
return 'an'; // 16-bit integer array (approximation)
}
else if (value instanceof Uint16Array) {
return 'aq'; // 16-bit unsigned integer array
}
else if (value instanceof Int16Array) {
return 'an'; // 16-bit integer array
}
else if (value instanceof BigUint64Array) {
return 'at'; // 64-bit unsigned integer array
}
else if (value instanceof BigInt64Array) {
return 'ax'; // 64-bit integer array
}
else if (Array.isArray(value) || (typeof value === 'object' && value !== null && 'length' in value && typeof value.length === 'number' && value.length >= 0)) {
// Array or TypedArray, attempt to infer child type
if (value.length === 0) {
return 'ai'; // Default empty array to integer array
}
// Check if it might be a dictionary array (each element is an object)
const firstItem = value[0];
if (typeof firstItem === 'object' && firstItem !== null && !Buffer.isBuffer(firstItem) && !(firstItem instanceof Uint8Array) && !('length' in firstItem && typeof firstItem.length === 'number')) {
const keys = Object.keys(firstItem);
if (keys.length > 0) {
// Infer as dictionary array a{sv}
return 'a{sv}';
}
}
// Check if all elements have the same type
const items = Array.from(value);
const firstType = this.inferType(firstItem);
const allSameType = items.every(item => this.inferType(item) === firstType);
if (!allSameType) {
// If types are inconsistent, infer as struct
const childTypes = items.map(item => this.inferType(item));
return `(${childTypes.join('')})`;
}
// Otherwise, treat as regular array with child type based on first element
return `a${firstType}`;
}
else if (typeof value === 'object' && value !== null) {
// Object, infer as dictionary array
const entries = Object.entries(value);
if (entries.length === 0) {
return 'a{sv}'; // Default empty object to string-variant dictionary
}
// Prefer to infer as dictionary array a{sv}, with string keys and variant values
return 'a{sv}';
}
else {
// Default to string type for unsupported or unknown types
return 's';
}
}
}
exports.DBusSignedValue = DBusSignedValue;