diff --git a/src/models/thing.ts b/src/models/thing.ts index 25e25522e..7de4e5981 100644 --- a/src/models/thing.ts +++ b/src/models/thing.ts @@ -153,7 +153,8 @@ export default class Thing extends EventEmitter { if (property.forms) { property.forms = property.forms.map((form) => { - // TODO: WebThingsIO non-standard keyword + // TODO: webthings proprietary field; + // See https://github.com/WebThingsIO/gateway/issues/2832 if (form.proxy) { delete form.proxy; form.href = `${Constants.PROXY_PATH}/${encodeURIComponent(this.id)}${form.href}`; @@ -768,7 +769,8 @@ export default class Thing extends EventEmitter { if (event.forms) { event.forms = event.forms.map((form) => { - // TODO: WebThingsIO non-standard keyword + // TODO: webthings proprietary field; + // See https://github.com/WebThingsIO/gateway/issues/2832 if (form.proxy) { delete form.proxy; form.href = `${Constants.PROXY_PATH}/${encodeURIComponent(this.id)}${form.href}`; diff --git a/static/js/constants.js b/static/js/constants.js index 0e41b5048..097d7c423 100644 --- a/static/js/constants.js +++ b/static/js/constants.js @@ -14,7 +14,8 @@ module.exports.ThingFormat = { }; module.exports.WoTOperation = { - READ_PROPERTY: 'readyproperty', + READ_PROPERTY: 'readproperty', + WRITE_PROPERTY: 'writeproperty', INVOKE_ACTION: 'invokeaction', READ_ALL_PROPERTIES: 'readallproperties', SUBSCRIBE_ALL_EVENTS: 'subscribeallevents', diff --git a/static/js/models/thing-model.js b/static/js/models/thing-model.js index 672e56d81..f9dd499ad 100644 --- a/static/js/models/thing-model.js +++ b/static/js/models/thing-model.js @@ -11,6 +11,7 @@ const API = require('../api').default; const App = require('../app'); const Model = require('./model'); const Constants = require('../constants'); +const Utils = require('../utils'); class ThingModel extends Model { constructor(description, ws) { @@ -194,7 +195,7 @@ class ThingModel extends Model { const property = this.propertyDescriptions[name]; - const href = property.forms[0].href; + const href = Utils.selectFormHref(property.forms, Constants.WoTOperation.READ_PROPERTY); return API.putJson(href, value) .then((json) => { @@ -216,7 +217,7 @@ class ThingModel extends Model { if (typeof this.propertiesHref === 'undefined') { const urls = Object.values(this.propertyDescriptions).map((v) => { if (v.forms) { - return v.forms[0].href; + return Utils.selectFormHref(v.forms, Constants.WoTOperation.WRITE_PROPERTY); } }); const requests = urls.map((u) => API.getJson(u)); diff --git a/static/js/rules/PropertySelect.js b/static/js/rules/PropertySelect.js index 3f61d9ea4..dbf8d4b39 100644 --- a/static/js/rules/PropertySelect.js +++ b/static/js/rules/PropertySelect.js @@ -2,6 +2,7 @@ const fluent = require('../fluent'); const RuleUtils = require('./RuleUtils'); const Units = require('../units'); const Utils = require('../utils'); +const Constants = require('../constants'); function propertyEqual(a, b) { if (!a && !b) { @@ -373,8 +374,12 @@ class PropertySelect { continue; } - property.id = RuleUtils.extractProperty(forms[0].href); - property.thing = RuleUtils.extractThing(forms[0].href); + property.id = RuleUtils.extractProperty( + Utils.selectFormHref(forms, Constants.WoTOperation.READ_PROPERTY) + ); + property.thing = RuleUtils.extractThing( + Utils.selectFormHref(forms, Constants.WoTOperation.READ_PROPERTY) + ); delete property.forms; const name = property.title || Utils.capitalize(property.name); diff --git a/static/js/schema-impl/capability/thing.js b/static/js/schema-impl/capability/thing.js index 495db19fc..29d0045d2 100644 --- a/static/js/schema-impl/capability/thing.js +++ b/static/js/schema-impl/capability/thing.js @@ -167,13 +167,7 @@ class Thing { } } - let href; - for (const form of property.forms) { - if (!form.op || form.op === Constants.WoTOperation.READ_PROPERTY) { - href = form.href; - break; - } - } + const href = Utils.selectFormHref(property.forms, Constants.WoTOperation.READ_PROPERTY); if (!href) { continue; @@ -300,13 +294,7 @@ class Thing { for (const name in description.actions) { const action = description.actions[name]; - let href; - for (const form of description.actions[name].forms) { - if (!form.op || form.op === Constants.WoTOperation.INVOKE_ACTION) { - href = form.href; - break; - } - } + const href = Utils.selectFormHref(action.forms, Constants.WoTOperation.INVOKE_ACTION); if (!href) { continue; diff --git a/static/js/utils.ts b/static/js/utils.ts index 64f77b036..5609dd01a 100644 --- a/static/js/utils.ts +++ b/static/js/utils.ts @@ -7,6 +7,7 @@ */ import * as Fluent from './fluent'; +import Constants from './constants'; /** * @param {String} str @@ -264,3 +265,37 @@ export function adjustInputValue(value: number, schema: Record) return value; } + +type WoTOperation = keyof typeof Constants.WoTOperation; +type WoTFormOperation = WoTOperation | WoTOperation[] | undefined; +/** + * Finds the gateway api endpoint for a specific operation. + * It uses the assumption that gateway enpoints are pushed to + * the form array as the last element. + * + * @param {Array} forms The list of forms. + * @param {string} operation + * @returns {string | undefined} the href of the select form or undefined if not found. + */ +export function selectFormHref( + forms: Array<{ href: string; op: WoTFormOperation }>, + operation: WoTOperation +): string | undefined { + return [...forms].reverse().find((selectedForm) => { + try { + const { protocol } = new URL(selectedForm.href); + return ( + protocol === 'http:' && + (!selectedForm.op || selectedForm.op === operation || selectedForm.op?.includes(operation)) + ); + } catch (error) { + if (error instanceof TypeError) { + // URL is relative or not well formatted + return ( + !selectedForm.op || selectedForm.op === operation || selectedForm.op?.includes(operation) + ); + } + throw error; + } + })?.href; +} diff --git a/static/js/views/things.js b/static/js/views/things.js index 0db066d4c..733cab114 100644 --- a/static/js/views/things.js +++ b/static/js/views/things.js @@ -215,13 +215,10 @@ const ThingsScreen = { return; } - let href; - for (const form of description.actions[actionName].forms) { - if (!form.op || form.op === Constants.WoTOperation.INVOKE_ACTION) { - href = form.href; - break; - } - } + const href = Utils.selectFormHref( + description.actions[actionName], + Constants.WoTOperation.INVOKE_ACTION + ); const icon = Icons.capabilityToIcon(description.selectedCapability); const iconEl = document.getElementById('thing-title-icon');