feat(device-core): add device ownership lifecycle
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user