140 lines
3.8 KiB
JavaScript
140 lines
3.8 KiB
JavaScript
export const DEVICE_LIFECYCLE_COMMAND_KINDS = Object.freeze([
|
|
"device.claim",
|
|
"device.transfer",
|
|
"discovery.reject",
|
|
"discovery.expire",
|
|
]);
|
|
|
|
const commandKindSet = new Set(DEVICE_LIFECYCLE_COMMAND_KINDS);
|
|
const keyPattern = /^[a-z][a-z0-9-]{1,62}$/;
|
|
const resolutionPattern = /^[a-z][a-z0-9._-]{1,63}$/;
|
|
|
|
export function isLifecycleManagementCommand(kind) {
|
|
return commandKindSet.has(kind);
|
|
}
|
|
|
|
export function normalizeLifecycleManagementCommand(kind, input) {
|
|
if (!commandKindSet.has(kind)) {
|
|
throw new TypeError("device_lifecycle_command_kind_invalid");
|
|
}
|
|
assertPlainObject(input);
|
|
|
|
if (kind === "device.claim") {
|
|
assertAllowedKeys(input, [
|
|
"projectRef",
|
|
"enrollmentIntentRef",
|
|
"discoveryRef",
|
|
"deviceKey",
|
|
"displayName",
|
|
]);
|
|
return Object.freeze({
|
|
projectId: normalizeEntityRef(input.projectRef, "project"),
|
|
enrollmentIntentId: normalizeEntityRef(
|
|
input.enrollmentIntentRef,
|
|
"enrollment-intent",
|
|
),
|
|
discoveryId: normalizeEntityRef(input.discoveryRef, "discovery"),
|
|
deviceKey: normalizePattern(
|
|
input.deviceKey,
|
|
keyPattern,
|
|
"device_key_invalid",
|
|
),
|
|
displayName: normalizeDisplayText(input.displayName, 160),
|
|
});
|
|
}
|
|
|
|
if (kind === "device.transfer") {
|
|
assertAllowedKeys(input, [
|
|
"deviceRef",
|
|
"sourceProjectRef",
|
|
"targetProjectRef",
|
|
"targetDeviceKey",
|
|
]);
|
|
const sourceProjectId = normalizeEntityRef(
|
|
input.sourceProjectRef,
|
|
"project",
|
|
);
|
|
const targetProjectId = normalizeEntityRef(
|
|
input.targetProjectRef,
|
|
"project",
|
|
);
|
|
if (sourceProjectId === targetProjectId) {
|
|
throw new TypeError("device_transfer_target_same_as_source");
|
|
}
|
|
return Object.freeze({
|
|
deviceId: normalizeEntityRef(input.deviceRef, "device"),
|
|
sourceProjectId,
|
|
targetProjectId,
|
|
targetDeviceKey: normalizePattern(
|
|
input.targetDeviceKey,
|
|
keyPattern,
|
|
"device_transfer_target_key_invalid",
|
|
),
|
|
});
|
|
}
|
|
|
|
assertAllowedKeys(input, [
|
|
"projectRef",
|
|
"discoveryRef",
|
|
"resolutionCode",
|
|
]);
|
|
return Object.freeze({
|
|
projectId: normalizeEntityRef(input.projectRef, "project"),
|
|
discoveryId: normalizeEntityRef(input.discoveryRef, "discovery"),
|
|
resolutionCode: normalizePattern(
|
|
input.resolutionCode,
|
|
resolutionPattern,
|
|
"device_discovery_resolution_code_invalid",
|
|
),
|
|
});
|
|
}
|
|
|
|
function normalizeEntityRef(value, prefix) {
|
|
if (typeof value !== "string") {
|
|
throw new TypeError(`device_${prefix}_ref_invalid`);
|
|
}
|
|
const match = value.match(new RegExp(
|
|
`^${prefix}:([0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})$`,
|
|
"i",
|
|
));
|
|
if (!match) throw new TypeError(`device_${prefix}_ref_invalid`);
|
|
return match[1].toLowerCase();
|
|
}
|
|
|
|
function normalizePattern(value, pattern, code) {
|
|
if (typeof value !== "string" || !pattern.test(value)) {
|
|
throw new TypeError(code);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function normalizeDisplayText(value, maxLength) {
|
|
if (typeof value !== "string") {
|
|
throw new TypeError("device_display_name_invalid");
|
|
}
|
|
const normalized = value.trim();
|
|
if (
|
|
normalized.length < 1
|
|
|| normalized.length > maxLength
|
|
|| /\u0000|[\u0001-\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(normalized)
|
|
) {
|
|
throw new TypeError("device_display_name_invalid");
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
function assertPlainObject(value) {
|
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
throw new TypeError("device_lifecycle_command_invalid");
|
|
}
|
|
}
|
|
|
|
function assertAllowedKeys(input, allowed) {
|
|
const allowedSet = new Set(allowed);
|
|
for (const key of Object.keys(input)) {
|
|
if (!allowedSet.has(key)) {
|
|
throw new TypeError(`device_management_command_field_unexpected:${key}`);
|
|
}
|
|
}
|
|
}
|