<commit_before>/** Manage inputs in a topology edition. */
define(function (require) {
  'use strict';
  var modules = require('modules');
  var _ = require('lodash');
  var angular = require('angular');

  modules.get('a4c-topology-editor', ['pascalprecht.translate', 'toaster']).factory('topoEditInputs',
    ['topologyServices', '$translate', 'toaster',
    function(topologyServices, $translate, toaster) {
      var TopologyEditorMixin = function(scope) {
        this.scope = scope;
      };

      TopologyEditorMixin.prototype = {
        constructor: TopologyEditorMixin,

        generateInputIdFromPropertyName: function(propertyName) {
          if (_.undefined(this.scope.topology.topology.inputs)) {
            return propertyName;
          }
          var i = 0;
          var inputId = propertyName;
          while (this.scope.topology.topology.inputs.hasOwnProperty(inputId)) {
            inputId = propertyName + '_' + i;
            i++;
          }
          return inputId;
        },

        createInput: function(inputId, propertyDefinition, callback) {
          var instance = this;
          topologyServices.inputs.add({
            topologyId: instance.scope.topology.topology.id,
            inputId: inputId
          }, angular.toJson(propertyDefinition), function(success) {
            if (!success.error) {
              instance.scope.refreshTopology(success.data, instance.scope.selectedNodeTemplate ? instance.scope.selectedNodeTemplate.name : undefined);
              callback();
            }
          });
        },

        createFromRelationshipProperty: function(relationshipName, propertyName) {
          var instance = this;
          var selectedRelationshipType = this.scope.topology.relationshipTypes[this.scope.selectedNodeTemplate.relationshipsMap[relationshipName].value.type];
          var selectedRelationshipPropertyDefinition = selectedRelationshipType.propertiesMap[propertyName].value;
          var inputId = this.generateInputIdFromPropertyName(propertyName);
          this.createInput(inputId, selectedRelationshipPropertyDefinition, function() {
            instance.scope.currentInputCandidatesForRelationshipProperty.push(inputId);
            instance.toggleRelationshipProperty(relationshipName, propertyName, inputId);
          });
        },

        createFromCapabilityProperty: function(capabilityName, propertyName) {
          var instance = this;
          var selectedCapabilityType = this.scope.topology.capabilityTypes[this.scope.selectedNodeTemplate.capabilitiesMap[capabilityName].value.type];
          var selectedCapabilityPropertyDefinition = selectedCapabilityType.propertiesMap[propertyName].value;
          var inputId = this.generateInputIdFromPropertyName(propertyName);
          this.createInput(inputId, selectedCapabilityPropertyDefinition, function() {
            instance.scope.currentInputCandidatesForCapabilityProperty.push(inputId);
            instance.toggleCapabilityProperty(capabilityName, propertyName, inputId);
          });
        },

        createFromProperty: function(propertyName) {
          var instance = this;
          var selectedNodeTemplateType = this.scope.topology.nodeTypes[this.scope.selectedNodeTemplate.type];
          var selectedPropertyDefinition = selectedNodeTemplateType.propertiesMap[propertyName].value;
          var inputId = this.generateInputIdFromPropertyName(propertyName);
          this.createInput(inputId, selectedPropertyDefinition, function() {
            instance.scope.currentInputCandidatesForProperty.push(inputId);
            instance.toggleProperty(propertyName, inputId);
          });
        },

        createFromArtifact: function(artifactId) {
          var instance = this;
          topologyServices.nodeTemplate.artifacts.setInput({
            topologyId: instance.scope.topology.topology.id,
            nodeTemplateName: instance.scope.selectedNodeTemplate.name,
            artifactId: artifactId,
            inputArtifactId: artifactId
          }, {}, function(result) {
            if (!result.error) {
              instance.scope.refreshTopology(result.data, instance.scope.selectedNodeTemplate ? instance.scope.selectedNodeTemplate.name : undefined);
            }
          });
        },

        getCandidatesForProperty: function(propertyName) {
          var instance = this;
          this.scope.currentInputCandidatesForProperty = [];
          topologyServices.nodeTemplate.getInputCandidates.getCandidates({
            topologyId: instance.scope.topology.topology.id,
            nodeTemplateName: instance.scope.selectedNodeTemplate.name,
            propertyId: propertyName
          }, function(success) {
            instance.scope.currentInputCandidatesForProperty = success.data;
          });
        },

        getCandidatesForArtifact: function(artifactId) {
          var instance = this;
          this.scope.currentInputCandidatesForArtifact = [];
          topologyServices.nodeTemplate.artifacts.getInputCandidates({
            topologyId: instance.scope.topology.topology.id,
            nodeTemplateName: instance.scope.selectedNodeTemplate.name,
            artifactId: artifactId
          }, function(success) {
            instance.scope.currentInputCandidatesForArtifact = success.data;
          });
        },

        getCandidatesForRelationshipProperty: function(relationshipName, propertyName) {
          var instance = this;
          this.scope.currentInputCandidatesForRelationshipProperty = [];
          topologyServices.nodeTemplate.relationship.getInputCandidates.getCandidates({
            topologyId: instance.scope.topology.topology.id,
            nodeTemplateName: instance.scope.selectedNodeTemplate.name,
            propertyId: propertyName,
            relationshipId: relationshipName
          }, function(success) {
            instance.scope.currentInputCandidatesForRelationshipProperty = success.data;
          });
        },

        getCandidatesForCapabilityProperty: function(capabilityName, propertyName) {
          var instance = this;
          this.scope.currentInputCandidatesForCapabilityProperty = [];
          topologyServices.nodeTemplate.capability.getInputCandidates.getCandidates({
            topologyId: instance.scope.topology.topology.id,
            nodeTemplateName: instance.scope.selectedNodeTemplate.name,
            propertyId: propertyName,
            capabilityId: capabilityName
          }, function(success) {
            instance.scope.currentInputCandidatesForCapabilityProperty = success.data;
          });
        },

        toggleProperty: function(propertyName, inputId) {
          var scope = this.scope;
          if (!this.isPropertyAssociatedToInput(propertyName, inputId)) {
            topologyServices.nodeTemplate.setInputs.set({
              topologyId: scope.topology.topology.id,
              inputId: inputId,
              nodeTemplateName: scope.selectedNodeTemplate.name,
              propertyId: propertyName
            }, function(success) {
              if (!success.error) {
                scope.refreshTopology(success.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
              }
            });
          } else {
            topologyServices.nodeTemplate.setInputs.unset({
              topologyId: scope.topology.topology.id,
              nodeTemplateName: scope.selectedNodeTemplate.name,
              propertyId: propertyName
            }, function(success) {
              if (!success.error) {
                scope.refreshTopology(success.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
              }
            });
          }
        },

        toggleArtifact: function(artifactId, inputArtifactId) {
          var scope = this.scope;
          if (!this.isArtifactAssociatedToInput(artifactId, inputArtifactId)) {
            topologyServices.nodeTemplate.artifacts.setInput({
              topologyId: scope.topology.topology.id,
              nodeTemplateName: scope.selectedNodeTemplate.name,
              artifactId: artifactId,
              inputArtifactId: inputArtifactId
            }, {}, function(result) {
              if (!result.error) {
                scope.refreshTopology(result.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
              }
            });
          } else {
            topologyServices.nodeTemplate.artifacts.unsetInput({
              topologyId: scope.topology.topology.id,
              nodeTemplateName: scope.selectedNodeTemplate.name,
              artifactId: artifactId,
              inputArtifactId: inputArtifactId
            }, {}, function(result) {
              if (!result.error) {
                scope.refreshTopology(result.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
              }
            });
          }
        },

        updateArtifactId: function(inputArtifactId, newId) {
          var scope = this.scope;
          if (inputArtifactId === newId) {
            return;
          }
          topologyServices.inputArtifacts.rename({
            topologyId: scope.topology.topology.id,
            inputArtifactId: inputArtifactId
          }, {newId: newId}, function(result) {
            if (!result.error) {
              scope.refreshTopology(result.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
            }
          });
        },

        removeArtifact: function(inputArtifactId) {
          var scope = this.scope;
          topologyServices.inputArtifacts.remove({
            topologyId: scope.topology.topology.id,
            inputArtifactId: inputArtifactId
          }, {}, function(result) {
            if (!result.error) {
              scope.refreshTopology(result.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
            }
          });
        },

        toggleRelationshipProperty: function(relationshipName, propertyName, inputId) {
          var scope = this.scope;
          if (!this.isRelationshipPropertyAssociatedToInput(relationshipName, propertyName, inputId)) {
            topologyServices.nodeTemplate.relationship.setInputs.set({
              topologyId: scope.topology.topology.id,
              inputId: inputId,
              nodeTemplateName: scope.selectedNodeTemplate.name,
              propertyId: propertyName,
              relationshipId: relationshipName
            }, function(success) {
              if (!success.error) {
                scope.refreshTopology(success.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
              }
            });
          } else {
            topologyServices.nodeTemplate.relationship.setInputs.unset({
              topologyId: scope.topology.topology.id,
              nodeTemplateName: scope.selectedNodeTemplate.name,
              propertyId: propertyName,
              relationshipId: relationshipName
            }, function(success) {
              if (!success.error) {
                scope.refreshTopology(success.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
              }
            });
          }
        },

        toggleCapabilityProperty: function(capabilityName, propertyName, inputId) {
          var scope = this.scope;
          if (!this.isCapabilityPropertyAssociatedToInput(capabilityName, propertyName, inputId)) {
            topologyServices.nodeTemplate.capability.setInputs.set({
              topologyId: scope.topology.topology.id,
              inputId: inputId,
              nodeTemplateName: scope.selectedNodeTemplate.name,
              propertyId: propertyName,
              capabilityId: capabilityName
            }, function(success) {
              if (!success.error) {
                scope.refreshTopology(success.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
              }
            });
          } else {
            topologyServices.nodeTemplate.capability.setInputs.unset({
              topologyId: scope.topology.topology.id,
              nodeTemplateName: scope.selectedNodeTemplate.name,
              propertyId: propertyName,
              capabilityId: capabilityName
            }, function(success) {
              if (!success.error) {
                scope.refreshTopology(success.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
              }
            });
          }
        },

        // check if the artifact is associated with an input.
        isInputArtifact: function(artifactId) {
          var scope = this.scope;
          var artifactName = scope.selectedNodeTemplate.artifacts[artifactId].artifactName;
          var regex = /\{\s*get_input_artifact:\s*(\w+)\s*\}/;
          var result = regex.exec(artifactName);
          return result !== null && result.length > 0;
        },

        // check if the id of the artifact input is the given one.
        isArtifactAssociatedToInput: function(artifactId, inputArtifactId) {
          var scope = this.scope;
          var artifactName = scope.selectedNodeTemplate.artifacts[artifactId].artifactName;
          var regex = /\{\s*get_input_artifact:\s*(\w+)\s*\}/;
          var result = regex.exec(artifactName);
          return result !== null && result.length > 0 && result[1] === inputArtifactId;
        },

        isPropertyValueIsAssociatedToInput: function(propertyValue, inputId) {
          if (_.defined(propertyValue) && _.defined(propertyValue.parameters) && propertyValue.parameters.length > 0) {
            return propertyValue.parameters[0] === inputId;
          }
          return false;
        },

        isPropertyAssociatedToInput: function(propertyName, inputId) {
          var scope = this.scope;
          var propertyValue = scope.selectedNodeTemplate.propertiesMap[propertyName].value;
          return this.isPropertyValueIsAssociatedToInput(propertyValue, inputId);
        },

        isRelationshipPropertyAssociatedToInput: function(relationshipName, propertyName, inputId) {
          var scope = this.scope;
          var propertyValue = scope.selectedNodeTemplate.relationshipsMap[relationshipName].value.propertiesMap[propertyName].value;
          return this.isPropertyValueIsAssociatedToInput(propertyValue, inputId);
        },

        isCapabilityPropertyAssociatedToInput: function(capabilityName, propertyName, inputId) {
          var scope = this.scope;
          var propertyValue = scope.selectedNodeTemplate.capabilitiesMap[capabilityName].value.propertiesMap[propertyName].value;
          return this.isPropertyValueIsAssociatedToInput(propertyValue, inputId);
        },

        remove: function(inputId) {
          var scope = this.scope;
          topologyServices.inputs.remove({
            topologyId: scope.topology.topology.id,
            inputId: inputId
          }, function(success) {
            if (!success.error) {
              scope.refreshTopology(success.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
            }
          });
        },

        update: function(oldInput, newInput, inputDefinition) {
          var scope = this.scope;
          if (newInput === oldInput) {
            return;
          }
          topologyServices.updateInput({
            topologyId: scope.topology.topology.id,
            inputId: oldInput,
            newInputId: newInput
          }, function(success) {
            if (!success.error) {
              scope.refreshTopology(success.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
            } else {
              inputDefinition.inputId = oldInput;
              var msg = $translate('ERRORS.' + success.error.code);
              toaster.pop('error', $translate(msg), $translate(msg), 6000, 'trustedHtml', null);
            }
          });
        }
      };

      return function(scope) {
        var instance = new TopologyEditorMixin(scope);
        scope.inputs = instance;
      };
    }
  ]); // modules
}); // define
<commit_msg>Fix bug in update of topology_inputs<commit_after>/** Manage inputs in a topology edition. */
define(function (require) {
  'use strict';
  var modules = require('modules');
  var _ = require('lodash');
  var angular = require('angular');

  modules.get('a4c-topology-editor', ['pascalprecht.translate', 'toaster']).factory('topoEditInputs',
    ['topologyServices', '$translate', 'toaster',
    function(topologyServices, $translate, toaster) {
      var TopologyEditorMixin = function(scope) {
        this.scope = scope;
      };

      TopologyEditorMixin.prototype = {
        constructor: TopologyEditorMixin,

        generateInputIdFromPropertyName: function(propertyName) {
          if (_.undefined(this.scope.topology.topology.inputs)) {
            return propertyName;
          }
          var i = 0;
          var inputId = propertyName;
          while (this.scope.topology.topology.inputs.hasOwnProperty(inputId)) {
            inputId = propertyName + '_' + i;
            i++;
          }
          return inputId;
        },

        createInput: function(inputId, propertyDefinition, callback) {
          var instance = this;
          topologyServices.inputs.add({
            topologyId: instance.scope.topology.topology.id,
            inputId: inputId
          }, angular.toJson(propertyDefinition), function(success) {
            if (!success.error) {
              instance.scope.refreshTopology(success.data, instance.scope.selectedNodeTemplate ? instance.scope.selectedNodeTemplate.name : undefined);
              callback();
            }
          });
        },

        createFromRelationshipProperty: function(relationshipName, propertyName) {
          var instance = this;
          var selectedRelationshipType = this.scope.topology.relationshipTypes[this.scope.selectedNodeTemplate.relationshipsMap[relationshipName].value.type];
          var selectedRelationshipPropertyDefinition = selectedRelationshipType.propertiesMap[propertyName].value;
          var inputId = this.generateInputIdFromPropertyName(propertyName);
          this.createInput(inputId, selectedRelationshipPropertyDefinition, function() {
            instance.scope.currentInputCandidatesForRelationshipProperty.push(inputId);
            instance.toggleRelationshipProperty(relationshipName, propertyName, inputId);
          });
        },

        createFromCapabilityProperty: function(capabilityName, propertyName) {
          var instance = this;
          var selectedCapabilityType = this.scope.topology.capabilityTypes[this.scope.selectedNodeTemplate.capabilitiesMap[capabilityName].value.type];
          var selectedCapabilityPropertyDefinition = selectedCapabilityType.propertiesMap[propertyName].value;
          var inputId = this.generateInputIdFromPropertyName(propertyName);
          this.createInput(inputId, selectedCapabilityPropertyDefinition, function() {
            instance.scope.currentInputCandidatesForCapabilityProperty.push(inputId);
            instance.toggleCapabilityProperty(capabilityName, propertyName, inputId);
          });
        },

        createFromProperty: function(propertyName) {
          var instance = this;
          var selectedNodeTemplateType = this.scope.topology.nodeTypes[this.scope.selectedNodeTemplate.type];
          var selectedPropertyDefinition = selectedNodeTemplateType.propertiesMap[propertyName].value;
          var inputId = this.generateInputIdFromPropertyName(propertyName);
          this.createInput(inputId, selectedPropertyDefinition, function() {
            instance.scope.currentInputCandidatesForProperty.push(inputId);
            instance.toggleProperty(propertyName, inputId);
          });
        },

        createFromArtifact: function(artifactId) {
          var instance = this;
          topologyServices.nodeTemplate.artifacts.setInput({
            topologyId: instance.scope.topology.topology.id,
            nodeTemplateName: instance.scope.selectedNodeTemplate.name,
            artifactId: artifactId,
            inputArtifactId: artifactId
          }, {}, function(result) {
            if (!result.error) {
              instance.scope.refreshTopology(result.data, instance.scope.selectedNodeTemplate ? instance.scope.selectedNodeTemplate.name : undefined);
            }
          });
        },

        getCandidatesForProperty: function(propertyName) {
          var instance = this;
          this.scope.currentInputCandidatesForProperty = [];
          topologyServices.nodeTemplate.getInputCandidates.getCandidates({
            topologyId: instance.scope.topology.topology.id,
            nodeTemplateName: instance.scope.selectedNodeTemplate.name,
            propertyId: propertyName
          }, function(success) {
            instance.scope.currentInputCandidatesForProperty = success.data;
          });
        },

        getCandidatesForArtifact: function(artifactId) {
          var instance = this;
          this.scope.currentInputCandidatesForArtifact = [];
          topologyServices.nodeTemplate.artifacts.getInputCandidates({
            topologyId: instance.scope.topology.topology.id,
            nodeTemplateName: instance.scope.selectedNodeTemplate.name,
            artifactId: artifactId
          }, function(success) {
            instance.scope.currentInputCandidatesForArtifact = success.data;
          });
        },

        getCandidatesForRelationshipProperty: function(relationshipName, propertyName) {
          var instance = this;
          this.scope.currentInputCandidatesForRelationshipProperty = [];
          topologyServices.nodeTemplate.relationship.getInputCandidates.getCandidates({
            topologyId: instance.scope.topology.topology.id,
            nodeTemplateName: instance.scope.selectedNodeTemplate.name,
            propertyId: propertyName,
            relationshipId: relationshipName
          }, function(success) {
            instance.scope.currentInputCandidatesForRelationshipProperty = success.data;
          });
        },

        getCandidatesForCapabilityProperty: function(capabilityName, propertyName) {
          var instance = this;
          this.scope.currentInputCandidatesForCapabilityProperty = [];
          topologyServices.nodeTemplate.capability.getInputCandidates.getCandidates({
            topologyId: instance.scope.topology.topology.id,
            nodeTemplateName: instance.scope.selectedNodeTemplate.name,
            propertyId: propertyName,
            capabilityId: capabilityName
          }, function(success) {
            instance.scope.currentInputCandidatesForCapabilityProperty = success.data;
          });
        },

        toggleProperty: function(propertyName, inputId) {
          var scope = this.scope;
          if (!this.isPropertyAssociatedToInput(propertyName, inputId)) {
            topologyServices.nodeTemplate.setInputs.set({
              topologyId: scope.topology.topology.id,
              inputId: inputId,
              nodeTemplateName: scope.selectedNodeTemplate.name,
              propertyId: propertyName
            }, function(success) {
              if (!success.error) {
                scope.refreshTopology(success.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
              }
            });
          } else {
            topologyServices.nodeTemplate.setInputs.unset({
              topologyId: scope.topology.topology.id,
              nodeTemplateName: scope.selectedNodeTemplate.name,
              propertyId: propertyName
            }, function(success) {
              if (!success.error) {
                scope.refreshTopology(success.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
              }
            });
          }
        },

        toggleArtifact: function(artifactId, inputArtifactId) {
          var scope = this.scope;
          if (!this.isArtifactAssociatedToInput(artifactId, inputArtifactId)) {
            topologyServices.nodeTemplate.artifacts.setInput({
              topologyId: scope.topology.topology.id,
              nodeTemplateName: scope.selectedNodeTemplate.name,
              artifactId: artifactId,
              inputArtifactId: inputArtifactId
            }, {}, function(result) {
              if (!result.error) {
                scope.refreshTopology(result.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
              }
            });
          } else {
            topologyServices.nodeTemplate.artifacts.unsetInput({
              topologyId: scope.topology.topology.id,
              nodeTemplateName: scope.selectedNodeTemplate.name,
              artifactId: artifactId,
              inputArtifactId: inputArtifactId
            }, {}, function(result) {
              if (!result.error) {
                scope.refreshTopology(result.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
              }
            });
          }
        },

        updateArtifactId: function(inputArtifactId, newId) {
          var scope = this.scope;
          if (inputArtifactId === newId) {
            return;
          }
          topologyServices.inputArtifacts.rename({
            topologyId: scope.topology.topology.id,
            inputArtifactId: inputArtifactId
          }, {newId: newId}, function(result) {
            if (!result.error) {
              scope.refreshTopology(result.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
            }
          });
        },

        removeArtifact: function(inputArtifactId) {
          var scope = this.scope;
          topologyServices.inputArtifacts.remove({
            topologyId: scope.topology.topology.id,
            inputArtifactId: inputArtifactId
          }, {}, function(result) {
            if (!result.error) {
              scope.refreshTopology(result.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
            }
          });
        },

        toggleRelationshipProperty: function(relationshipName, propertyName, inputId) {
          var scope = this.scope;
          if (!this.isRelationshipPropertyAssociatedToInput(relationshipName, propertyName, inputId)) {
            topologyServices.nodeTemplate.relationship.setInputs.set({
              topologyId: scope.topology.topology.id,
              inputId: inputId,
              nodeTemplateName: scope.selectedNodeTemplate.name,
              propertyId: propertyName,
              relationshipId: relationshipName
            }, function(success) {
              if (!success.error) {
                scope.refreshTopology(success.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
              }
            });
          } else {
            topologyServices.nodeTemplate.relationship.setInputs.unset({
              topologyId: scope.topology.topology.id,
              nodeTemplateName: scope.selectedNodeTemplate.name,
              propertyId: propertyName,
              relationshipId: relationshipName
            }, function(success) {
              if (!success.error) {
                scope.refreshTopology(success.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
              }
            });
          }
        },

        toggleCapabilityProperty: function(capabilityName, propertyName, inputId) {
          var scope = this.scope;
          if (!this.isCapabilityPropertyAssociatedToInput(capabilityName, propertyName, inputId)) {
            topologyServices.nodeTemplate.capability.setInputs.set({
              topologyId: scope.topology.topology.id,
              inputId: inputId,
              nodeTemplateName: scope.selectedNodeTemplate.name,
              propertyId: propertyName,
              capabilityId: capabilityName
            }, function(success) {
              if (!success.error) {
                scope.refreshTopology(success.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
              }
            });
          } else {
            topologyServices.nodeTemplate.capability.setInputs.unset({
              topologyId: scope.topology.topology.id,
              nodeTemplateName: scope.selectedNodeTemplate.name,
              propertyId: propertyName,
              capabilityId: capabilityName
            }, function(success) {
              if (!success.error) {
                scope.refreshTopology(success.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
              }
            });
          }
        },

        // check if the artifact is associated with an input.
        isInputArtifact: function(artifactId) {
          var scope = this.scope;
          var artifactName = scope.selectedNodeTemplate.artifacts[artifactId].artifactName;
          var regex = /\{\s*get_input_artifact:\s*(\w+)\s*\}/;
          var result = regex.exec(artifactName);
          return result !== null && result.length > 0;
        },

        // check if the id of the artifact input is the given one.
        isArtifactAssociatedToInput: function(artifactId, inputArtifactId) {
          var scope = this.scope;
          var artifactName = scope.selectedNodeTemplate.artifacts[artifactId].artifactName;
          var regex = /\{\s*get_input_artifact:\s*(\w+)\s*\}/;
          var result = regex.exec(artifactName);
          return result !== null && result.length > 0 && result[1] === inputArtifactId;
        },

        isPropertyValueIsAssociatedToInput: function(propertyValue, inputId) {
          if (_.defined(propertyValue) && _.defined(propertyValue.parameters) && propertyValue.parameters.length > 0) {
            return propertyValue.parameters[0] === inputId;
          }
          return false;
        },

        isPropertyAssociatedToInput: function(propertyName, inputId) {
          var scope = this.scope;
          var propertyValue = scope.selectedNodeTemplate.propertiesMap[propertyName].value;
          return this.isPropertyValueIsAssociatedToInput(propertyValue, inputId);
        },

        isRelationshipPropertyAssociatedToInput: function(relationshipName, propertyName, inputId) {
          var scope = this.scope;
          var propertyValue = scope.selectedNodeTemplate.relationshipsMap[relationshipName].value.propertiesMap[propertyName].value;
          return this.isPropertyValueIsAssociatedToInput(propertyValue, inputId);
        },

        isCapabilityPropertyAssociatedToInput: function(capabilityName, propertyName, inputId) {
          var scope = this.scope;
          var propertyValue = scope.selectedNodeTemplate.capabilitiesMap[capabilityName].value.propertiesMap[propertyName].value;
          return this.isPropertyValueIsAssociatedToInput(propertyValue, inputId);
        },

        remove: function(inputId) {
          var scope = this.scope;
          topologyServices.inputs.remove({
            topologyId: scope.topology.topology.id,
            inputId: inputId
          }, function(success) {
            if (!success.error) {
              scope.refreshTopology(success.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
            }
          });
        },

        update: function(oldInput, newInput, inputDefinition) {
          var scope = this.scope;
          if (newInput === oldInput) {
            return;
          }
          topologyServices.inputs.update({
            topologyId: scope.topology.topology.id,
            inputId: oldInput,
            newInputId: newInput
          }, function(success) {
            if (!success.error) {
              scope.refreshTopology(success.data, scope.selectedNodeTemplate ? scope.selectedNodeTemplate.name : undefined);
            } else {
              inputDefinition.inputId = oldInput;
              var msg = $translate('ERRORS.' + success.error.code);
              toaster.pop('error', $translate(msg), $translate(msg), 6000, 'trustedHtml', null);
            }
          });
        }
      };

      return function(scope) {
        var instance = new TopologyEditorMixin(scope);
        scope.inputs = instance;
      };
    }
  ]); // modules
}); // define
<|endoftext|>


<commit_before>var chai = require('chai'),
//	expect = chai.expect,
	path = require('path');
var GambleActions = require('../src/GambleActions.js');

describe('GambleActions', function() {
	var gambleActions;
	before(function(done) {
		gambleActions = new GambleActions();
		done();
	});
	describe('isNormalInteger', function(){

		it("should be true with a number greater than 0", function() {
			var result = gambleActions.isNormalInteger("5");
			expect(result).to.equal(true);
		});

	})

});
<commit_msg>Test is working<commit_after>var chai = require('chai'),
	expect = chai.expect,
	path = require('path');
var GambleActions = require('../src/GambleActions.js');

describe('GambleActions', function() {

	var gambleActions;
	beforeEach(function() {
		gambleActions = new GambleActions.default();
	});
	describe('isNormalInteger', function(){

		it("should be true with a number greater than 0", function() {
			var result = gambleActions.isNormalInteger("5");
			expect(result).to.equal(true);
		});

	})

});
<|endoftext|>


<commit_before>// @flow
import { createSelector } from 'reselect';
import {
  makeSelectClaimForUri,
  selectClaimsByUri,
  makeSelectClaimsInChannelForCurrentPageState,
  makeSelectClaimIsNsfw,
  makeSelectRecommendedContentForUri,
  makeSelectMediaTypeForUri,
  selectBlockedChannels,
  parseURI,
} from 'lbry-redux';
import { selectAllCostInfoByUri } from 'lbryinc';
import { selectShowMatureContent } from 'redux/selectors/settings';

const RECENT_HISTORY_AMOUNT = 10;
const HISTORY_ITEMS_PER_PAGE = 50;

export const selectState = (state: any) => state.content || {};

export const selectPlayingUri = createSelector(
  selectState,
  state => state.playingUri
);

export const makeSelectIsPlaying = (uri: string) =>
  createSelector(
    selectPlayingUri,
    playingUri => playingUri === uri
  );

export const makeSelectContentPositionForUri = (uri: string) =>
  createSelector(
    selectState,
    makeSelectClaimForUri(uri),
    (state, claim) => {
      if (!claim) {
        return null;
      }
      const outpoint = `${claim.txid}:${claim.nout}`;
      const id = claim.claim_id;
      return state.positions[id] ? state.positions[id][outpoint] : null;
    }
  );

export const selectHistory = createSelector(
  selectState,
  state => state.history || []
);

export const selectHistoryPageCount = createSelector(
  selectHistory,
  history => Math.ceil(history.length / HISTORY_ITEMS_PER_PAGE)
);

export const makeSelectHistoryForPage = (page: number) =>
  createSelector(
    selectHistory,
    selectClaimsByUri,
    (history, claimsByUri) => {
      const left = page * HISTORY_ITEMS_PER_PAGE;
      const historyItemsForPage = history.slice(left, left + HISTORY_ITEMS_PER_PAGE);
      return historyItemsForPage;
    }
  );

export const makeSelectHistoryForUri = (uri: string) =>
  createSelector(
    selectHistory,
    history => history.find(i => i.uri === uri)
  );

export const makeSelectHasVisitedUri = (uri: string) =>
  createSelector(
    makeSelectHistoryForUri(uri),
    history => Boolean(history)
  );

export const makeSelectNextUnplayedRecommended = (uri: string) =>
  createSelector(
    makeSelectRecommendedContentForUri(uri),
    selectHistory,
    selectClaimsByUri,
    selectAllCostInfoByUri,
    selectBlockedChannels,
    (
      recommendedForUri: Array<string>,
      history: Array<{ uri: string }>,
      claimsByUri: { [string]: ?Claim },
      costInfoByUri: { [string]: { cost: 0 | string } },
      blockedChannels: Array<string>
    ) => {
      if (recommendedForUri) {
        // Make sure we don't autoplay paid content, channels, or content from blocked channels
        for (let i = 0; i < recommendedForUri.length; i++) {
          const recommendedUri = recommendedForUri[i];

          const { isChannel } = parseURI(recommendedUri);
          if (isChannel) {
            continue;
          }

          const costInfo = costInfoByUri[recommendedUri];
          if (!costInfo || costInfo.cost !== 0) {
            continue;
          }

          const claim = claimsByUri[recommendedUri];
          const channel = claim && claim.signing_channel;
          if (channel && blockedChannels.find(blockedUri => blockedUri === channel.permanent_url)) {
            continue;
          }

          if (!history.find(item => item.uri === recommendedForUri[i])) {
            return recommendedForUri[i];
          }
        }
      }
    }
  );

export const selectRecentHistory = createSelector(
  selectHistory,
  history => {
    return history.slice(0, RECENT_HISTORY_AMOUNT);
  }
);

export const makeSelectCategoryListUris = (uris: ?Array<string>, channel: string) =>
  createSelector(
    makeSelectClaimsInChannelForCurrentPageState(channel),
    channelClaims => {
      if (uris) return uris;

      if (channelClaims) {
        const CATEGORY_LIST_SIZE = 10;
        return channelClaims.slice(0, CATEGORY_LIST_SIZE).map(({ name, claim_id: claimId }) => `${name}#${claimId}`);
      }

      return null;
    }
  );

export const makeSelectShouldObscurePreview = (uri: string) =>
  createSelector(
    selectShowMatureContent,
    makeSelectClaimIsNsfw(uri),
    (showMatureContent, isClaimMature) => {
      return isClaimMature && !showMatureContent;
    }
  );

export const makeSelectCanAutoplay = (uri: string) =>
  createSelector(
    makeSelectMediaTypeForUri(uri),
    mediaType => {
      const canAutoPlay = ['audio', 'video', 'image', 'text', 'document'].includes(mediaType);
      return canAutoPlay;
    }
  );

export const makeSelectIsText = (uri: string) =>
  createSelector(
    makeSelectMediaTypeForUri(uri),
    mediaType => {
      const isText = ['text', 'document'].includes(mediaType);
      return isText;
    }
  );
<commit_msg>find => some<commit_after>// @flow
import { createSelector } from 'reselect';
import {
  makeSelectClaimForUri,
  selectClaimsByUri,
  makeSelectClaimsInChannelForCurrentPageState,
  makeSelectClaimIsNsfw,
  makeSelectRecommendedContentForUri,
  makeSelectMediaTypeForUri,
  selectBlockedChannels,
  parseURI,
} from 'lbry-redux';
import { selectAllCostInfoByUri } from 'lbryinc';
import { selectShowMatureContent } from 'redux/selectors/settings';

const RECENT_HISTORY_AMOUNT = 10;
const HISTORY_ITEMS_PER_PAGE = 50;

export const selectState = (state: any) => state.content || {};

export const selectPlayingUri = createSelector(
  selectState,
  state => state.playingUri
);

export const makeSelectIsPlaying = (uri: string) =>
  createSelector(
    selectPlayingUri,
    playingUri => playingUri === uri
  );

export const makeSelectContentPositionForUri = (uri: string) =>
  createSelector(
    selectState,
    makeSelectClaimForUri(uri),
    (state, claim) => {
      if (!claim) {
        return null;
      }
      const outpoint = `${claim.txid}:${claim.nout}`;
      const id = claim.claim_id;
      return state.positions[id] ? state.positions[id][outpoint] : null;
    }
  );

export const selectHistory = createSelector(
  selectState,
  state => state.history || []
);

export const selectHistoryPageCount = createSelector(
  selectHistory,
  history => Math.ceil(history.length / HISTORY_ITEMS_PER_PAGE)
);

export const makeSelectHistoryForPage = (page: number) =>
  createSelector(
    selectHistory,
    selectClaimsByUri,
    (history, claimsByUri) => {
      const left = page * HISTORY_ITEMS_PER_PAGE;
      const historyItemsForPage = history.slice(left, left + HISTORY_ITEMS_PER_PAGE);
      return historyItemsForPage;
    }
  );

export const makeSelectHistoryForUri = (uri: string) =>
  createSelector(
    selectHistory,
    history => history.find(i => i.uri === uri)
  );

export const makeSelectHasVisitedUri = (uri: string) =>
  createSelector(
    makeSelectHistoryForUri(uri),
    history => Boolean(history)
  );

export const makeSelectNextUnplayedRecommended = (uri: string) =>
  createSelector(
    makeSelectRecommendedContentForUri(uri),
    selectHistory,
    selectClaimsByUri,
    selectAllCostInfoByUri,
    selectBlockedChannels,
    (
      recommendedForUri: Array<string>,
      history: Array<{ uri: string }>,
      claimsByUri: { [string]: ?Claim },
      costInfoByUri: { [string]: { cost: 0 | string } },
      blockedChannels: Array<string>
    ) => {
      if (recommendedForUri) {
        // Make sure we don't autoplay paid content, channels, or content from blocked channels
        for (let i = 0; i < recommendedForUri.length; i++) {
          const recommendedUri = recommendedForUri[i];

          const { isChannel } = parseURI(recommendedUri);
          if (isChannel) {
            continue;
          }

          const costInfo = costInfoByUri[recommendedUri];
          if (!costInfo || costInfo.cost !== 0) {
            continue;
          }

          const claim = claimsByUri[recommendedUri];
          const channel = claim && claim.signing_channel;
          if (channel && blockedChannels.some(blockedUri => blockedUri === channel.permanent_url)) {
            continue;
          }

          if (!history.some(item => item.uri === recommendedForUri[i])) {
            return recommendedForUri[i];
          }
        }
      }
    }
  );

export const selectRecentHistory = createSelector(
  selectHistory,
  history => {
    return history.slice(0, RECENT_HISTORY_AMOUNT);
  }
);

export const makeSelectCategoryListUris = (uris: ?Array<string>, channel: string) =>
  createSelector(
    makeSelectClaimsInChannelForCurrentPageState(channel),
    channelClaims => {
      if (uris) return uris;

      if (channelClaims) {
        const CATEGORY_LIST_SIZE = 10;
        return channelClaims.slice(0, CATEGORY_LIST_SIZE).map(({ name, claim_id: claimId }) => `${name}#${claimId}`);
      }

      return null;
    }
  );

export const makeSelectShouldObscurePreview = (uri: string) =>
  createSelector(
    selectShowMatureContent,
    makeSelectClaimIsNsfw(uri),
    (showMatureContent, isClaimMature) => {
      return isClaimMature && !showMatureContent;
    }
  );

export const makeSelectCanAutoplay = (uri: string) =>
  createSelector(
    makeSelectMediaTypeForUri(uri),
    mediaType => {
      const canAutoPlay = ['audio', 'video', 'image', 'text', 'document'].includes(mediaType);
      return canAutoPlay;
    }
  );

export const makeSelectIsText = (uri: string) =>
  createSelector(
    makeSelectMediaTypeForUri(uri),
    mediaType => {
      const isText = ['text', 'document'].includes(mediaType);
      return isText;
    }
  );
<|endoftext|>


<commit_before>/*
* Copyright 2010-2011 Research In Motion Limited.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
 * @namespace The Connection object gives access to the device's cellular and wifi connection information.
 * @toc {Connection} Connection
 * @featureID blackberry.connection
 */
blackberry.connection = {

        /**
         * @description The type of network connection that is being used by the application.
         * @type Number
         * @readOnly
         * @BB10X
         */
        type : 0,

        /**
         * @default 0
         * @type Number
         * @constant
         * @BB10X
         * @description BlackBerry Bridge connection
         */

         BB : 0,
        /**
         * @default 1
         * @type Number
         * @constant
         * @BB10X
         * @description Bluetooth tethering connection
         */

         BLUETOOTH_DUN : 1,
        /**
         * @default 2
         * @type Number
         * @constant
         * @BB10X
         * @description Cellular connection
         */

         CELLULAR : 2,
        /**
         * @default 3
         * @type Number
         * @constant
         * @BB10X
         * @description Wired ethernet connection
         */

         ETHERNET : 3,
        /**
         * @default 4
         * @type Number
         * @constant
         * @BB10X
         * @description No connection
         */

         NONE : 4,
        /**
         * @default 5
         * @type Number
         * @constant
         * @BB10X
         * @description Unknown connection
         */
        UNKNOWN : 5,
        /**
         * @default 6
         * @type Number
         * @constant
         * @BB10X
         * @description USB connection
         */

         USB : 6,
        /**
         * @default 7
         * @type Number
         * @constant
         * @BB10X
         * @description VPN connection
         */

         VPN : 7,
        /**
         * @default 8
         * @type Number
         * @constant
         * @BB10X
         * @description Wi-fi connection
         */

         WIFI : 8
}
<commit_msg>Fixed constant values<commit_after>/*
* Copyright 2010-2011 Research In Motion Limited.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
 * @namespace The Connection object gives access to the device's cellular and wifi connection information.
 * @toc {Connection} Connection
 * @featureID blackberry.connection
 */
blackberry.connection = {

        /**
         * @description The type of network connection that is being used by the application.
         * @type Number
         * @readOnly
         * @BB10X
         */
        type : 0,

        /**
         * @type Number
         * @constant
         * @BB10X
         * @description BlackBerry Bridge connection
         */

         BB : 6,
        /**
         * @type Number
         * @constant
         * @BB10X
         * @description Bluetooth tethering connection
         */

         BLUETOOTH_DUN : 3,
        /**
         * @type Number
         * @constant
         * @BB10X
         * @description Cellular connection
         */

         CELLULAR : 7,
        /**
         * @type Number
         * @constant
         * @BB10X
         * @description Wired ethernet connection
         */

         ETHERNET : 1,
        /**
         * @type Number
         * @constant
         * @BB10X
         * @description No connection
         */

         NONE : 8,
        /**
         * @type Number
         * @constant
         * @BB10X
         * @description Unknown connection
         */
        UNKNOWN : 0,
        /**
         * @type Number
         * @constant
         * @BB10X
         * @description USB connection
         */

         USB : 4,
        /**
         * @type Number
         * @constant
         * @BB10X
         * @description VPN connection
         */

         VPN : 5,
        /**
         * @type Number
         * @constant
         * @BB10X
         * @description Wi-fi connection
         */

         WIFI : 2
}
<|endoftext|>


<commit_before>'use strict';

async function wait(millis) {
  return new Promise((resolve) => {
    setTimeout(resolve, millis);
  });
}

function clamp(value, min, max) {
  return Math.min(Math.max(value, min), max);
};

window.addEventListener('load', async () => {
  const sliderContainer = document.getElementById('slider-container');

  const reconnectContainer = document.getElementById('reconnect-container');
  const reconnectAttemptElement = document.getElementById('reconnect-attempt');
  const reconnectMessage = document.getElementById('reconnecting-message');

  const connectContainer = document.getElementById('connect-container');
  const connectButton = document.getElementById('connect-button');
  const connectErrorMessage = document.getElementById('connect-error-message');

  const legoTrain = document.getElementById('lego-train');
  const legoWire = document.getElementById('lego-wire');

  let wireDirection = parseInt(Cookies.get('wire-direction'));
  if (isNaN(wireDirection)) {
    wireDirection = -1;
  }
  let connectorPosition = parseInt(Cookies.get('connector-position'));
  if (isNaN(connectorPosition)) {
    connectorPosition = -1;
  }

  function updateLegoUi() {
    Cookies.set('wire-direction', wireDirection, {expires: 365});
    Cookies.set('connector-position', connectorPosition, {expires: 365});

    let scale = 1;
    let translateX = 0;
    if (wireDirection === 1 && connectorPosition === 1) {
      translateX = 100;
      scale = 1;
    } else if (wireDirection === 1 && connectorPosition === -1) {
      translateX = -35;
      scale = 1;
    } else if (wireDirection === -1 && connectorPosition === 1) {
      translateX = 35;
      scale = -1;
    } else if (wireDirection === -1 && connectorPosition === -1) {
      translateX = -100;
      scale = -1;
    }
    legoWire.style.setProperty('transform', `translateX(${translateX}px) scaleX(${scale})`);
    legoTrain.style.setProperty('transform', `scaleX(${connectorPosition})`);
  }
  updateLegoUi();
  legoTrain.classList.add('show');
  legoWire.classList.add('show');

  legoTrain.addEventListener('click', function(event) {
    connectorPosition *= -1;
    updateLegoUi();
  });

  legoWire.addEventListener('click', function(event) {
    wireDirection *= -1;
    updateLegoUi();
  });

  let slider = null;
  let currentValue = null;
  const bluetooth = new Bluetooth();
  bluetooth.addEventListener('connect', () => {
    if (slider) {
      slider.setValue(currentValue, true);
    }

    sliderContainer.style.setProperty('display', '');
    connectContainer.style.setProperty('display', 'none', 'important');
    reconnectContainer.style.setProperty('display', 'none', 'important');
  });

  let reconnectAttempt;
  bluetooth.addEventListener('disconnect', () => {
    reconnectAttempt = 1;
    reconnectMessage.innerText = '';
    sliderContainer.style.setProperty('display', 'none', 'important');
    reconnectContainer.style.setProperty('display', '');
  });

  bluetooth.addEventListener('reconnecting', (event) => {
    reconnectAttemptElement.innerText = `#${reconnectAttempt}`;
    reconnectAttempt++;
  });

  bluetooth.addEventListener('reconnect-failed', (event) => {
    reconnectMessage.innerText = event.detail.message;
  });

  connectButton.addEventListener('click', async () => {
    connectErrorMessage.innerHTML = '';
    connectButton.disabled = true;

    try {
      await bluetooth.connect();

      const values = 17; // positive, odd number
      const factor = 1; // > 0 && <= 1
      const lowerStart = 0.45;

      const options = [];
      const middle = (values - 1) / 2;
      for (let i = 0; i < values; i++) {
        let distance = -(i - middle);
        if (distance === -0) {
          distance = 0;
        }
        const percentage = Math.round(distance / middle * 10000) / 10000;
        const absPercentage = Math.abs(percentage);

        let value = (factor * percentage / (1 + lowerStart) + lowerStart * Math.sign(distance)) * -1;
        if (wireDirection === 1 && connectorPosition === 1) {
          value *= 1;
        } else if (wireDirection === 1 && connectorPosition === -1) {
          value *= 1;
        } else if (wireDirection === -1 && connectorPosition === 1) {
          value *= -1;
        } else if (wireDirection === -1 && connectorPosition === -1) {
          value *= -1;
        }

        let backgroundColor;
        if (distance === 0) {
          backgroundColor = ``;
        } else if (distance > 0) {
          backgroundColor = `rgba(${Math.round(absPercentage * 255)}, ${Math.round((1 - absPercentage) * 255)}, 0, 0.6)`;
        } else {
          backgroundColor = `rgba(0, ${Math.round((1 - absPercentage) * 255)}, ${Math.round(absPercentage * 255)}, 0.6)`;
        }
        options.push({
          label: `${Math.round(percentage * 100)} %`,
          value,
          backgroundColor,
        });
      }

      console.log(options);
      currentValue = options[middle].value;
      slider = new Slider(document.getElementById('slider'), {options});
      slider.addEventListener('change', (event) => {
        currentValue = event.detail.value;
        bluetooth.writeValue(event.detail.value);
      });
      slider.setValue(currentValue);
    } catch (e) {
      console.error(e);
      connectErrorMessage.innerText = e.message;
      connectButton.disabled = false;
    }
  });
});

class Bluetooth {
  #elementForListeners = document.createElement('div');

  #device = null;
  #characteristic = null;

  #writePromise = Promise.resolve();

  #currentValue = null;
  #latestValueToWrite = null;

  async connect() {
    console.log('Pressed', navigator, navigator.bluetooth);

    this.#device = await navigator.bluetooth.requestDevice({
      optionalServices: ['0bd51666-e7cb-469b-8e4d-2742f1ba77cc'],
      acceptAllDevices: true,
    });
    console.log('Device', this.#device);

    this.#device.addEventListener('gattserverdisconnected', () => {
      console.log('Disconnected');
      this.#characteristic = null;

      this.#elementForListeners.dispatchEvent(new CustomEvent('disconnect', {}));
      this.#reconnect();
    });
    await this.#connect();
  }

  async #connect() {
    console.log('Connecting');
    const server = await this.#device.gatt.connect();
    console.log('Server', server);

    const service = await server.getPrimaryService('0bd51666-e7cb-469b-8e4d-2742f1ba77cc');
    console.log('Service', service);

    for (const characteristic of await service.getCharacteristics('e7add780-b042-4876-aae1-112855353cc1')) {
      console.log('Characteristic', characteristic);
      this.#characteristic = characteristic;
    }

    this.#elementForListeners.dispatchEvent(new CustomEvent('connect', {}));
  }

  async #reconnect() {
    let reconnectWait = 1000;
    while (true) {
      this.#elementForListeners.dispatchEvent(new CustomEvent('reconnecting', {}));
      try {
        console.log('Reconnecting');
        await this.#connect();
        break;
      } catch (e) {
        console.error(e);
        this.#elementForListeners.dispatchEvent(new CustomEvent('reconnect-failed', {detail: e}));
      }
      await wait(reconnectWait);
      reconnectWait = Math.min(reconnectWait + 200, 5000);
    }
  }

  isConnected() {
    return !!this.#characteristic;
  }

  async writeValue(value) {
    if (!this.isConnected()) {
      throw Error(`Bluetooth not connected`);
    }

    this.#latestValueToWrite = value;
    this.#writePromise = this.#writePromise
      .then(async () => {
        if (value === this.#latestValueToWrite && value !== this.#currentValue) {
          if (this.#currentValue === 0) {
            let startUpBoostValue = clamp(value * 1.5, -1, 1);
            console.log('Sending start up boost: ' + startUpBoostValue);
            await this.#characteristic.writeValue(new TextEncoder('utf-8').encode(startUpBoostValue));
            await wait(10);
          }
          console.log('Sending: ' + value);
          await this.#characteristic.writeValue(new TextEncoder('utf-8').encode(value));
          this.#currentValue = value;
          await wait(20);
        }
      })
      .catch(console.error);
    return this.#writePromise;
  }

  addEventListener(type, listener) {
    this.#elementForListeners.addEventListener(type, listener);
  }

  removeEventListener(type, listener) {
    this.#elementForListeners.addEventListener(type, listener);
  }
}

class Slider {
  #elementForListeners = document.createElement('div');
  #lastTriggerTarget = null;

  #steps = [];
  #middleIndex = null;

  constructor(element, {options, middle = null}) {
    if (middle === null) {
      this.#middleIndex = (options.length - 1) / 2;
    }
    for (const option of options) {
      const stepElement = document.createElement('div');
      stepElement.classList.add('step', 'd-flex', 'justify-content-center', 'align-items-center', 'flex-grow-1');
      stepElement.setAttribute('slider-value', option.value);
      stepElement.setAttribute('slider-background-color', option.backgroundColor);
      stepElement.innerHTML = `<span class="label text-right">${option.label}</span>`;
      element.append(stepElement);
      this.#steps.push({
        element: stepElement,
        options: option,
      });
    }

    const mouseDown = (event) => {
      element.addEventListener('mousemove', mouseMove);
      element.addEventListener('touchmove', touchMove);

      element.addEventListener('mouseup', mouseUp);
      element.addEventListener('touchend', mouseUp);

      this.#triggerChangeEvent(event.target);
    };

    const mouseMove = event => {
      this.#triggerChangeEvent(event.target);
    };

    const touchMove = event => {
      const touch = event.touches[0];
      const target = document.elementFromPoint(touch.pageX, touch.pageY);
      this.#triggerChangeEvent(target);
    };

    const mouseUp = () => {
      element.removeEventListener('mousemove', mouseMove);
      element.removeEventListener('touchmove', touchMove);

      element.removeEventListener('mouseup', mouseUp);
      element.removeEventListener('touchend', mouseUp);
    };

    element.addEventListener('mousedown', mouseDown);
    element.addEventListener('touchstart', mouseDown);
  }

  setValue(value, forceTrigger = false) {
    for (const step of this.#steps) {
      if (step.options.value === value) {
        this.#triggerChangeEvent(step.element, forceTrigger);
        break;
      }
    }
  }

  addEventListener(type, listener) {
    this.#elementForListeners.addEventListener(type, listener);
  }

  removeEventListener(type, listener) {
    this.#elementForListeners.addEventListener(type, listener);
  }

  #triggerChangeEvent(target, forceTrigger = false) {
    if (!target) {
      return;
    }
    while (!target.classList.contains('step')) {
      if (!target) {
        return;
      }
      target = target.parentElement;
    }
    if (!forceTrigger && target === this.#lastTriggerTarget) {
      return;
    }
    this.#lastTriggerTarget = target;

    const stepElementIndex = this.#steps.findIndex((step) => step.element === target);
    for (let i = 0; i < this.#steps.length; i++) {
      const step = this.#steps[i];
      if (i >= stepElementIndex && i <= this.#middleIndex || i <= stepElementIndex && i >= this.#middleIndex) {
        step.element.classList.add('active');
        step.element.style.setProperty('background-color', step.element.getAttribute('slider-background-color'));
      } else {
        step.element.classList.remove('active');
        step.element.style.setProperty('background-color', '');
      }
    }

    const rawValue = target.getAttribute('slider-value');
    const event = new CustomEvent('change', {
      detail: {
        target,
        rawValue,
        value: isNaN(parseFloat(rawValue)) ? rawValue : parseFloat(rawValue),
      },
    });
    this.#elementForListeners.dispatchEvent(event);
  }
}
<commit_msg>[bluetooth] Decreased start boost<commit_after>'use strict';

async function wait(millis) {
  return new Promise((resolve) => {
    setTimeout(resolve, millis);
  });
}

function clamp(value, min, max) {
  return Math.min(Math.max(value, min), max);
};

window.addEventListener('load', async () => {
  const sliderContainer = document.getElementById('slider-container');

  const reconnectContainer = document.getElementById('reconnect-container');
  const reconnectAttemptElement = document.getElementById('reconnect-attempt');
  const reconnectMessage = document.getElementById('reconnecting-message');

  const connectContainer = document.getElementById('connect-container');
  const connectButton = document.getElementById('connect-button');
  const connectErrorMessage = document.getElementById('connect-error-message');

  const legoTrain = document.getElementById('lego-train');
  const legoWire = document.getElementById('lego-wire');

  let wireDirection = parseInt(Cookies.get('wire-direction'));
  if (isNaN(wireDirection)) {
    wireDirection = -1;
  }
  let connectorPosition = parseInt(Cookies.get('connector-position'));
  if (isNaN(connectorPosition)) {
    connectorPosition = -1;
  }

  function updateLegoUi() {
    Cookies.set('wire-direction', wireDirection, {expires: 365});
    Cookies.set('connector-position', connectorPosition, {expires: 365});

    let scale = 1;
    let translateX = 0;
    if (wireDirection === 1 && connectorPosition === 1) {
      translateX = 100;
      scale = 1;
    } else if (wireDirection === 1 && connectorPosition === -1) {
      translateX = -35;
      scale = 1;
    } else if (wireDirection === -1 && connectorPosition === 1) {
      translateX = 35;
      scale = -1;
    } else if (wireDirection === -1 && connectorPosition === -1) {
      translateX = -100;
      scale = -1;
    }
    legoWire.style.setProperty('transform', `translateX(${translateX}px) scaleX(${scale})`);
    legoTrain.style.setProperty('transform', `scaleX(${connectorPosition})`);
  }
  updateLegoUi();
  legoTrain.classList.add('show');
  legoWire.classList.add('show');

  legoTrain.addEventListener('click', function(event) {
    connectorPosition *= -1;
    updateLegoUi();
  });

  legoWire.addEventListener('click', function(event) {
    wireDirection *= -1;
    updateLegoUi();
  });

  let slider = null;
  let currentValue = null;
  const bluetooth = new Bluetooth();
  bluetooth.addEventListener('connect', () => {
    if (slider) {
      slider.setValue(currentValue, true);
    }

    sliderContainer.style.setProperty('display', '');
    connectContainer.style.setProperty('display', 'none', 'important');
    reconnectContainer.style.setProperty('display', 'none', 'important');
  });

  let reconnectAttempt;
  bluetooth.addEventListener('disconnect', () => {
    reconnectAttempt = 1;
    reconnectMessage.innerText = '';
    sliderContainer.style.setProperty('display', 'none', 'important');
    reconnectContainer.style.setProperty('display', '');
  });

  bluetooth.addEventListener('reconnecting', (event) => {
    reconnectAttemptElement.innerText = `#${reconnectAttempt}`;
    reconnectAttempt++;
  });

  bluetooth.addEventListener('reconnect-failed', (event) => {
    reconnectMessage.innerText = event.detail.message;
  });

  connectButton.addEventListener('click', async () => {
    connectErrorMessage.innerHTML = '';
    connectButton.disabled = true;

    try {
      await bluetooth.connect();

      const values = 17; // positive, odd number
      const factor = 1; // > 0 && <= 1
      const lowerStart = 0.45;

      const options = [];
      const middle = (values - 1) / 2;
      for (let i = 0; i < values; i++) {
        let distance = -(i - middle);
        if (distance === -0) {
          distance = 0;
        }
        const percentage = Math.round(distance / middle * 10000) / 10000;
        const absPercentage = Math.abs(percentage);

        let value = (factor * percentage / (1 + lowerStart) + lowerStart * Math.sign(distance)) * -1;
        if (wireDirection === 1 && connectorPosition === 1) {
          value *= 1;
        } else if (wireDirection === 1 && connectorPosition === -1) {
          value *= 1;
        } else if (wireDirection === -1 && connectorPosition === 1) {
          value *= -1;
        } else if (wireDirection === -1 && connectorPosition === -1) {
          value *= -1;
        }

        let backgroundColor;
        if (distance === 0) {
          backgroundColor = ``;
        } else if (distance > 0) {
          backgroundColor = `rgba(${Math.round(absPercentage * 255)}, ${Math.round((1 - absPercentage) * 255)}, 0, 0.6)`;
        } else {
          backgroundColor = `rgba(0, ${Math.round((1 - absPercentage) * 255)}, ${Math.round(absPercentage * 255)}, 0.6)`;
        }
        options.push({
          label: `${Math.round(percentage * 100)} %`,
          value,
          backgroundColor,
        });
      }

      console.log(options);
      currentValue = options[middle].value;
      slider = new Slider(document.getElementById('slider'), {options});
      slider.addEventListener('change', (event) => {
        currentValue = event.detail.value;
        bluetooth.writeValue(event.detail.value);
      });
      slider.setValue(currentValue);
    } catch (e) {
      console.error(e);
      connectErrorMessage.innerText = e.message;
      connectButton.disabled = false;
    }
  });
});

class Bluetooth {
  #elementForListeners = document.createElement('div');

  #device = null;
  #characteristic = null;

  #writePromise = Promise.resolve();

  #currentValue = null;
  #latestValueToWrite = null;

  async connect() {
    console.log('Pressed', navigator, navigator.bluetooth);

    this.#device = await navigator.bluetooth.requestDevice({
      optionalServices: ['0bd51666-e7cb-469b-8e4d-2742f1ba77cc'],
      acceptAllDevices: true,
    });
    console.log('Device', this.#device);

    this.#device.addEventListener('gattserverdisconnected', () => {
      console.log('Disconnected');
      this.#characteristic = null;

      this.#elementForListeners.dispatchEvent(new CustomEvent('disconnect', {}));
      this.#reconnect();
    });
    await this.#connect();
  }

  async #connect() {
    console.log('Connecting');
    const server = await this.#device.gatt.connect();
    console.log('Server', server);

    const service = await server.getPrimaryService('0bd51666-e7cb-469b-8e4d-2742f1ba77cc');
    console.log('Service', service);

    for (const characteristic of await service.getCharacteristics('e7add780-b042-4876-aae1-112855353cc1')) {
      console.log('Characteristic', characteristic);
      this.#characteristic = characteristic;
    }

    this.#elementForListeners.dispatchEvent(new CustomEvent('connect', {}));
  }

  async #reconnect() {
    let reconnectWait = 1000;
    while (true) {
      this.#elementForListeners.dispatchEvent(new CustomEvent('reconnecting', {}));
      try {
        console.log('Reconnecting');
        await this.#connect();
        break;
      } catch (e) {
        console.error(e);
        this.#elementForListeners.dispatchEvent(new CustomEvent('reconnect-failed', {detail: e}));
      }
      await wait(reconnectWait);
      reconnectWait = Math.min(reconnectWait + 200, 5000);
    }
  }

  isConnected() {
    return !!this.#characteristic;
  }

  async writeValue(value) {
    if (!this.isConnected()) {
      throw Error(`Bluetooth not connected`);
    }

    this.#latestValueToWrite = value;
    this.#writePromise = this.#writePromise
      .then(async () => {
        if (value === this.#latestValueToWrite && value !== this.#currentValue) {
          // if (this.#currentValue === 0) {
          //   let startUpBoostValue = clamp(value * 1.1, -1, 1);
          //   console.log('Sending start up boost: ' + startUpBoostValue);
          //   await this.#characteristic.writeValue(new TextEncoder('utf-8').encode(startUpBoostValue));
          //   await wait(10);
          // }
          console.log('Sending: ' + value);
          await this.#characteristic.writeValue(new TextEncoder('utf-8').encode(value));
          this.#currentValue = value;
          await wait(20);
        }
      })
      .catch(console.error);
    return this.#writePromise;
  }

  addEventListener(type, listener) {
    this.#elementForListeners.addEventListener(type, listener);
  }

  removeEventListener(type, listener) {
    this.#elementForListeners.addEventListener(type, listener);
  }
}

class Slider {
  #elementForListeners = document.createElement('div');
  #lastTriggerTarget = null;

  #steps = [];
  #middleIndex = null;

  constructor(element, {options, middle = null}) {
    if (middle === null) {
      this.#middleIndex = (options.length - 1) / 2;
    }
    for (const option of options) {
      const stepElement = document.createElement('div');
      stepElement.classList.add('step', 'd-flex', 'justify-content-center', 'align-items-center', 'flex-grow-1');
      stepElement.setAttribute('slider-value', option.value);
      stepElement.setAttribute('slider-background-color', option.backgroundColor);
      stepElement.innerHTML = `<span class="label text-right">${option.label}</span>`;
      element.append(stepElement);
      this.#steps.push({
        element: stepElement,
        options: option,
      });
    }

    const mouseDown = (event) => {
      element.addEventListener('mousemove', mouseMove);
      element.addEventListener('touchmove', touchMove);

      element.addEventListener('mouseup', mouseUp);
      element.addEventListener('touchend', mouseUp);

      this.#triggerChangeEvent(event.target);
    };

    const mouseMove = event => {
      this.#triggerChangeEvent(event.target);
    };

    const touchMove = event => {
      const touch = event.touches[0];
      const target = document.elementFromPoint(touch.pageX, touch.pageY);
      this.#triggerChangeEvent(target);
    };

    const mouseUp = () => {
      element.removeEventListener('mousemove', mouseMove);
      element.removeEventListener('touchmove', touchMove);

      element.removeEventListener('mouseup', mouseUp);
      element.removeEventListener('touchend', mouseUp);
    };

    element.addEventListener('mousedown', mouseDown);
    element.addEventListener('touchstart', mouseDown);
  }

  setValue(value, forceTrigger = false) {
    for (const step of this.#steps) {
      if (step.options.value === value) {
        this.#triggerChangeEvent(step.element, forceTrigger);
        break;
      }
    }
  }

  addEventListener(type, listener) {
    this.#elementForListeners.addEventListener(type, listener);
  }

  removeEventListener(type, listener) {
    this.#elementForListeners.addEventListener(type, listener);
  }

  #triggerChangeEvent(target, forceTrigger = false) {
    if (!target) {
      return;
    }
    while (!target.classList.contains('step')) {
      if (!target) {
        return;
      }
      target = target.parentElement;
    }
    if (!forceTrigger && target === this.#lastTriggerTarget) {
      return;
    }
    this.#lastTriggerTarget = target;

    const stepElementIndex = this.#steps.findIndex((step) => step.element === target);
    for (let i = 0; i < this.#steps.length; i++) {
      const step = this.#steps[i];
      if (i >= stepElementIndex && i <= this.#middleIndex || i <= stepElementIndex && i >= this.#middleIndex) {
        step.element.classList.add('active');
        step.element.style.setProperty('background-color', step.element.getAttribute('slider-background-color'));
      } else {
        step.element.classList.remove('active');
        step.element.style.setProperty('background-color', '');
      }
    }

    const rawValue = target.getAttribute('slider-value');
    const event = new CustomEvent('change', {
      detail: {
        target,
        rawValue,
        value: isNaN(parseFloat(rawValue)) ? rawValue : parseFloat(rawValue),
      },
    });
    this.#elementForListeners.dispatchEvent(event);
  }
}
<|endoftext|>


<commit_before>const sleep = require("sleep-promise");

const cluster = require("cluster");

const JSONRPC = require("../..");

const TestClient = require("./TestClient");

const assert = require("assert");

module.exports =
class TestEndpoint extends JSONRPC.EndpointBase
{
	/**
	 * @param {boolean} bBenchmarkMode
	 */
	constructor(bBenchmarkMode)
	{
		super(
			/*strName*/ "Test",
			/*strPath*/ "/api",
			/*objReflection*/ {},
			/*classReverseCallsClient*/ TestClient
		);

		this._bBenchmarkMode = !!bBenchmarkMode;

		this.fnResolveWaitForWebPage = null;
		this.nWaitForWebPageRemainingCallsCount = null;

		/*******************************************************
		 * WebRTC stuff:
		 *******************************************************/
		this._arrLonelyHeartWebRTCClients = [];
		this._objHookedUpRTCPairs = {};
		this._nWebRTCConnectionID = 0;

		this._nWebRTCCupidonHeartBeat = setInterval(
			async () => {
				while(this._arrLonelyHeartWebRTCClients.length >= 2)
				{
					const objMale = this._arrLonelyHeartWebRTCClients.pop();
					const objFemale = this._arrLonelyHeartWebRTCClients.pop();
					const nRTCConnectionID = ++this._nWebRTCConnectionID;

					const arrIceServers = [
						{
							urls: "turn:192.168.137.3:3478",
							username: "guest",
							credential: "thirdeye"
						}
					];

					try
					{
						this._objHookedUpRTCPairs[nRTCConnectionID] = {
							objMale: objMale,
							objFemale: objFemale
						};

						const objOffer = await objMale.client.rpc("makeOffer", [nRTCConnectionID, arrIceServers]);
						assert(objOffer.type === "offer", "Not an offer.");

						const objAnswer = await objFemale.client.rpc("makeAnswer", [nRTCConnectionID, objOffer, arrIceServers]);
						assert(objAnswer.type === "answer", "Not an answer.");

						await objMale.client.rpc("thatsWhatSheSaid", [nRTCConnectionID, objAnswer]);

						objMale.client.plugins[0].webSocket.on(
							"close",
							async (nCode, strReason) => {
								if(objFemale.client.plugins[0].webSocket.readyState === JSONRPC.WebSocketAdapters.WebSocketWrapperBase.OPEN)
								{
									delete this._objHookedUpRTCPairs[nRTCConnectionID];
									await objFemale.client.rpc("breakUpRTCConnection", [nRTCConnectionID]);
								}
							}
						);
						objFemale.client.plugins[0].webSocket.on(
							"close",
							async (nCode, strReason) => {
								if(objMale.client.plugins[0].webSocket.readyState === JSONRPC.WebSocketAdapters.WebSocketWrapperBase.OPEN)
								{
									delete this._objHookedUpRTCPairs[nRTCConnectionID];
									await objMale.client.rpc("breakUpRTCConnection", [nRTCConnectionID]);
								}
							}
						);
					}
					catch(error)
					{
						objMale.fnReject(error);
						objFemale.fnReject(error);

						delete this._objHookedUpRTCPairs[nRTCConnectionID];
					}
				}
			},
			1000
		);
	}

	/**
	 * Hello world?
	 *
	 * @param {JSONRPC.IncomingRequest} incomingRequest
	 * @param {string} strReturn
	 * @param {boolean} bRandomSleep
	 * @param {string|null} strATeamCharacterName
	 *
	 * @returns {string}
	 */
	async ping(incomingRequest, strReturn, bRandomSleep, strATeamCharacterName)
	{
		if(bRandomSleep && !this._bBenchmarkMode)
		{
			await sleep(parseInt(Math.random() * 1000 /*milliseconds*/, 10));
		}

		if(typeof strATeamCharacterName === "string")
		{
			await incomingRequest.reverseCallsClient.rpc("ping", [strATeamCharacterName + " called back to confirm this: " + strReturn + "!", /*bRandomSleep*/ true]);
		}
		else if(strReturn === "Calling from html es5 client, bidirectional websocket mode.")
		{
			await incomingRequest.reverseCallsClient.rpc("ping", ["This is node. You, the browser, called back earlier to confirm this: " + strReturn + "!", /*bRandomSleep*/ false, "CallMeBackOnceAgain"]);
		}

		if(
			this.fnResolveWaitForWebPage !== null
			&& --this.nWaitForWebPageRemainingCallsCount === 0
		)
		{
			this.fnResolveWaitForWebPage();
			this.fnResolveWaitForWebPage = null;
		}

		return strReturn;
	}

	/**
	 * Hello world?
	 *
	 * @param {JSONRPC.IncomingRequest} incomingRequest
	 *
	 * @returns {string}
	 */
	async throwJSONRPCException(incomingRequest)
	{
		throw new JSONRPC.Exception("JSONRPC.Exception", JSONRPC.Exception.INTERNAL_ERROR);
	}

	/**
	 * Hello world?
	 *
	 * @param {JSONRPC.IncomingRequest} incomingRequest
	 *
	 * @returns {string}
	 */
	async throwError(incomingRequest)
	{
		throw new Error("Error");
	}

	/**
	 * If a reverseCallsClient is available, obtain the WebSocket from it and close it.
	 *
	 * If bTerminate is true, terminate the WebSocket instead of closing it.
	 *
	 * @param {JSONRPC.IncomingRequest} incomingRequest
	 * @param {boolean} bTerminate
	 *
	 * @returns {null}
	 */
	async closeConnection(incomingRequest, bTerminate)
	{
		bTerminate = !!bTerminate;

		if(incomingRequest.reverseCallsClient)
		{
			for(let plugin of incomingRequest.reverseCallsClient.plugins)
			{
				if(plugin instanceof JSONRPC.Plugins.Client.WebSocketTransport)
				{
					if(bTerminate)
					{
						plugin.webSocket.terminate();
					}
					else
					{
						if(plugin.webSocket.readyState === JSONRPC.WebSocketAdapters.WebSocketWrapperBase.OPEN)
						{
							plugin.webSocket.close(
								/* CloseEvent.Internal Error */ 1011,
								"[TestEndpoint.closeConnection()] Intentionally closing websocket for testing."
							);
						}
					}
				}
			}
		}

		return null;
	}

	/**
	 * If a reverseCallsClient is available, obtain the WebSocket from it and terminate it (send FIN packet).
	 *
	 * @param {JSONRPC.IncomingRequest} incomingRequest
	 *
	 * @returns {null}
	 */
	async terminateConnection(incomingRequest)
	{
		return this.closeConnection(incomingRequest, /*bTerminate*/ true);
	}

	/**
	 * Authentication function.
	 *
	 * It is intercepted by ServerPluginAuthorizeWebSocketAndClientMultiton.
	 * If it doesn't throw, it will remember that the websocket connection is authenticated.
	 *
	 * @param {JSONRPC.IncomingRequest} incomingRequest
	 * @param {string} strTeamMember
	 * @param {string} strSecretKnock
	 * @param {boolean} bDoNotAuthorizeMe
	 *
	 * @returns {{teamMember: {string}}}
	 */
	async ImHereForTheParty(incomingRequest, strTeamMember, strSecretKnock, bDoNotAuthorizeMe)
	{
		const arrTheATeam = ["Hannibal", "Face", "Baracus", "Murdock", "Lynch"];

		if(!arrTheATeam.includes(strTeamMember))
		{
			throw new JSONRPC.Exception("We don't let strangers in.", JSONRPC.Exception.NOT_AUTHENTICATED);
		}

		if(strSecretKnock !== (strTeamMember + " does the harlem shake"))
		{
			throw new JSONRPC.Exception("You don't dance like " + strTeamMember + ". Who are you?", JSONRPC.Exception.NOT_AUTHENTICATED);
		}

		return {
			"teamMember": strTeamMember
		};
	}

	/**
	 * @param {JSONRPC.IncomingRequest} incomingRequest
	 * @param {number} nPID
	 */
	async killWorker(incomingRequest, nPID)
	{
		if(!cluster.isMaster)
		{
			throw new Error("Only available on the master.");
		}

		for(let worker of cluster.workers)
		{
			if(worker.pid === nPID)
			{
				worker.kill();
				return;
			}
		}

		throw new Error("Worker with pid " + nPID + " not found.");
	}

	/**
	 * Register a lonely client.
	 *
	 * @param {JSONRPC.IncomingRequest} incomingRequest
	 * @returns {null}
	 */
	async cupidonTheWebRTCConnection(incomingRequest)
	{
		return new Promise((fnResolve, fnReject) => {
			const objCupidonPending = {
				client: incomingRequest.reverseCallsClient,
				fnResolve: fnResolve,
				fnReject: fnReject,
				nWebSocketConnectionID: incomingRequest.connectionID
			};

			this._arrLonelyHeartWebRTCClients.push(objCupidonPending);
		});
	}

	/**
	 * @param {JSONRPC.IncomingRequest} incomingRequest
	 * @param {number} nConnectionID
	 * @param {Object} objRTCIceCandidate
	 *
	 * @returns {null}
	 */
	async webRTCAddIceCandidate(incomingRequest, nRTCConnectionID, objRTCIceCandidate)
	{
		if(!this._objHookedUpRTCPairs[nRTCConnectionID])
		{
			throw new Error("Could not find connection ID " + JSON.stringify(nRTCConnectionID));
		}

		try
		{
			if(incomingRequest.connectionID === this._objHookedUpRTCPairs[nRTCConnectionID].objMale.nWebSocketConnectionID)
			{
				await this._objHookedUpRTCPairs[nRTCConnectionID].objFemale.client.rpc("webRTCAddIceCandidate", [nRTCConnectionID, objRTCIceCandidate]);
			}
			else if(incomingRequest.connectionID === this._objHookedUpRTCPairs[nRTCConnectionID].objFemale.nWebSocketConnectionID)
			{
				await this._objHookedUpRTCPairs[nRTCConnectionID].objMale.client.rpc("webRTCAddIceCandidate", [nRTCConnectionID, objRTCIceCandidate]);
			}
			else
			{
				throw new JSONRPC.Exception("You are not authorized to access connection ID " + nRTCConnectionID, JSONRPC.Exception.NOT_AUTHORIZED);
			}
		}
		catch(error)
		{
			this._objHookedUpRTCPairs[nRTCConnectionID].objMale.fnReject(error);
			this._objHookedUpRTCPairs[nRTCConnectionID].objFemale.fnReject(error);

			throw error;
		}
	}

	/**
	 * @param {JSONRPC.IncomingRequest} incomingRequest
	 * @param {number} nRTCConnectionID
	 *
	 * @returns {null}
	 */
	async femaleDataChannelIsOpen(incomingRequest, nRTCConnectionID)
	{
		if(incomingRequest.connectionID === this._objHookedUpRTCPairs[nRTCConnectionID].objFemale.nWebSocketConnectionID)
		{
			try
			{
				await this._objHookedUpRTCPairs[nRTCConnectionID].objMale.client.rpc("femaleDataChannelIsOpen", [nRTCConnectionID]);
			}
			catch(error)
			{
				this._objHookedUpRTCPairs[nRTCConnectionID].objMale.fnReject(error);
				this._objHookedUpRTCPairs[nRTCConnectionID].objFemale.fnReject(error);

				throw error;
			}

			this._objHookedUpRTCPairs[nRTCConnectionID].objMale.fnResolve(nRTCConnectionID);
			this._objHookedUpRTCPairs[nRTCConnectionID].objFemale.fnResolve(nRTCConnectionID);
		}
		else
		{
			const error = new Error("Unknown connection ID, or you are not authorized to signal the channel is open.");

			this._objHookedUpRTCPairs[nRTCConnectionID].objMale.fnReject(error);
			this._objHookedUpRTCPairs[nRTCConnectionID].objFemale.fnReject(error);

			throw error;
		}
	}
};
<commit_msg>Removed unneeded ICE servers in tests.<commit_after>const sleep = require("sleep-promise");

const cluster = require("cluster");

const JSONRPC = require("../..");

const TestClient = require("./TestClient");

const assert = require("assert");

module.exports =
class TestEndpoint extends JSONRPC.EndpointBase
{
	/**
	 * @param {boolean} bBenchmarkMode
	 */
	constructor(bBenchmarkMode)
	{
		super(
			/*strName*/ "Test",
			/*strPath*/ "/api",
			/*objReflection*/ {},
			/*classReverseCallsClient*/ TestClient
		);

		this._bBenchmarkMode = !!bBenchmarkMode;

		this.fnResolveWaitForWebPage = null;
		this.nWaitForWebPageRemainingCallsCount = null;

		/*******************************************************
		 * WebRTC stuff:
		 *******************************************************/
		this._arrLonelyHeartWebRTCClients = [];
		this._objHookedUpRTCPairs = {};
		this._nWebRTCConnectionID = 0;

		this._nWebRTCCupidonHeartBeat = setInterval(
			async () => {
				while(this._arrLonelyHeartWebRTCClients.length >= 2)
				{
					const objMale = this._arrLonelyHeartWebRTCClients.pop();
					const objFemale = this._arrLonelyHeartWebRTCClients.pop();
					const nRTCConnectionID = ++this._nWebRTCConnectionID;

					const arrIceServers = [
						/*{
							urls: "turn:192.168.137.3:3478",
							username: "guest",
							credential: "thirdeye"
						}*/
					];

					try
					{
						this._objHookedUpRTCPairs[nRTCConnectionID] = {
							objMale: objMale,
							objFemale: objFemale
						};

						const objOffer = await objMale.client.rpc("makeOffer", [nRTCConnectionID, arrIceServers]);
						assert(objOffer.type === "offer", "Not an offer.");

						const objAnswer = await objFemale.client.rpc("makeAnswer", [nRTCConnectionID, objOffer, arrIceServers]);
						assert(objAnswer.type === "answer", "Not an answer.");

						await objMale.client.rpc("thatsWhatSheSaid", [nRTCConnectionID, objAnswer]);

						objMale.client.plugins[0].webSocket.on(
							"close",
							async (nCode, strReason) => {
								if(objFemale.client.plugins[0].webSocket.readyState === JSONRPC.WebSocketAdapters.WebSocketWrapperBase.OPEN)
								{
									delete this._objHookedUpRTCPairs[nRTCConnectionID];
									await objFemale.client.rpc("breakUpRTCConnection", [nRTCConnectionID]);
								}
							}
						);
						objFemale.client.plugins[0].webSocket.on(
							"close",
							async (nCode, strReason) => {
								if(objMale.client.plugins[0].webSocket.readyState === JSONRPC.WebSocketAdapters.WebSocketWrapperBase.OPEN)
								{
									delete this._objHookedUpRTCPairs[nRTCConnectionID];
									await objMale.client.rpc("breakUpRTCConnection", [nRTCConnectionID]);
								}
							}
						);
					}
					catch(error)
					{
						objMale.fnReject(error);
						objFemale.fnReject(error);

						delete this._objHookedUpRTCPairs[nRTCConnectionID];
					}
				}
			},
			1000
		);
	}

	/**
	 * Hello world?
	 *
	 * @param {JSONRPC.IncomingRequest} incomingRequest
	 * @param {string} strReturn
	 * @param {boolean} bRandomSleep
	 * @param {string|null} strATeamCharacterName
	 *
	 * @returns {string}
	 */
	async ping(incomingRequest, strReturn, bRandomSleep, strATeamCharacterName)
	{
		if(bRandomSleep && !this._bBenchmarkMode)
		{
			await sleep(parseInt(Math.random() * 1000 /*milliseconds*/, 10));
		}

		if(typeof strATeamCharacterName === "string")
		{
			await incomingRequest.reverseCallsClient.rpc("ping", [strATeamCharacterName + " called back to confirm this: " + strReturn + "!", /*bRandomSleep*/ true]);
		}
		else if(strReturn === "Calling from html es5 client, bidirectional websocket mode.")
		{
			await incomingRequest.reverseCallsClient.rpc("ping", ["This is node. You, the browser, called back earlier to confirm this: " + strReturn + "!", /*bRandomSleep*/ false, "CallMeBackOnceAgain"]);
		}

		if(
			this.fnResolveWaitForWebPage !== null
			&& --this.nWaitForWebPageRemainingCallsCount === 0
		)
		{
			this.fnResolveWaitForWebPage();
			this.fnResolveWaitForWebPage = null;
		}

		return strReturn;
	}

	/**
	 * Hello world?
	 *
	 * @param {JSONRPC.IncomingRequest} incomingRequest
	 *
	 * @returns {string}
	 */
	async throwJSONRPCException(incomingRequest)
	{
		throw new JSONRPC.Exception("JSONRPC.Exception", JSONRPC.Exception.INTERNAL_ERROR);
	}

	/**
	 * Hello world?
	 *
	 * @param {JSONRPC.IncomingRequest} incomingRequest
	 *
	 * @returns {string}
	 */
	async throwError(incomingRequest)
	{
		throw new Error("Error");
	}

	/**
	 * If a reverseCallsClient is available, obtain the WebSocket from it and close it.
	 *
	 * If bTerminate is true, terminate the WebSocket instead of closing it.
	 *
	 * @param {JSONRPC.IncomingRequest} incomingRequest
	 * @param {boolean} bTerminate
	 *
	 * @returns {null}
	 */
	async closeConnection(incomingRequest, bTerminate)
	{
		bTerminate = !!bTerminate;

		if(incomingRequest.reverseCallsClient)
		{
			for(let plugin of incomingRequest.reverseCallsClient.plugins)
			{
				if(plugin instanceof JSONRPC.Plugins.Client.WebSocketTransport)
				{
					if(bTerminate)
					{
						plugin.webSocket.terminate();
					}
					else
					{
						if(plugin.webSocket.readyState === JSONRPC.WebSocketAdapters.WebSocketWrapperBase.OPEN)
						{
							plugin.webSocket.close(
								/* CloseEvent.Internal Error */ 1011,
								"[TestEndpoint.closeConnection()] Intentionally closing websocket for testing."
							);
						}
					}
				}
			}
		}

		return null;
	}

	/**
	 * If a reverseCallsClient is available, obtain the WebSocket from it and terminate it (send FIN packet).
	 *
	 * @param {JSONRPC.IncomingRequest} incomingRequest
	 *
	 * @returns {null}
	 */
	async terminateConnection(incomingRequest)
	{
		return this.closeConnection(incomingRequest, /*bTerminate*/ true);
	}

	/**
	 * Authentication function.
	 *
	 * It is intercepted by ServerPluginAuthorizeWebSocketAndClientMultiton.
	 * If it doesn't throw, it will remember that the websocket connection is authenticated.
	 *
	 * @param {JSONRPC.IncomingRequest} incomingRequest
	 * @param {string} strTeamMember
	 * @param {string} strSecretKnock
	 * @param {boolean} bDoNotAuthorizeMe
	 *
	 * @returns {{teamMember: {string}}}
	 */
	async ImHereForTheParty(incomingRequest, strTeamMember, strSecretKnock, bDoNotAuthorizeMe)
	{
		const arrTheATeam = ["Hannibal", "Face", "Baracus", "Murdock", "Lynch"];

		if(!arrTheATeam.includes(strTeamMember))
		{
			throw new JSONRPC.Exception("We don't let strangers in.", JSONRPC.Exception.NOT_AUTHENTICATED);
		}

		if(strSecretKnock !== (strTeamMember + " does the harlem shake"))
		{
			throw new JSONRPC.Exception("You don't dance like " + strTeamMember + ". Who are you?", JSONRPC.Exception.NOT_AUTHENTICATED);
		}

		return {
			"teamMember": strTeamMember
		};
	}

	/**
	 * @param {JSONRPC.IncomingRequest} incomingRequest
	 * @param {number} nPID
	 */
	async killWorker(incomingRequest, nPID)
	{
		if(!cluster.isMaster)
		{
			throw new Error("Only available on the master.");
		}

		for(let worker of cluster.workers)
		{
			if(worker.pid === nPID)
			{
				worker.kill();
				return;
			}
		}

		throw new Error("Worker with pid " + nPID + " not found.");
	}

	/**
	 * Register a lonely client.
	 *
	 * @param {JSONRPC.IncomingRequest} incomingRequest
	 * @returns {null}
	 */
	async cupidonTheWebRTCConnection(incomingRequest)
	{
		return new Promise((fnResolve, fnReject) => {
			const objCupidonPending = {
				client: incomingRequest.reverseCallsClient,
				fnResolve: fnResolve,
				fnReject: fnReject,
				nWebSocketConnectionID: incomingRequest.connectionID
			};

			this._arrLonelyHeartWebRTCClients.push(objCupidonPending);
		});
	}

	/**
	 * @param {JSONRPC.IncomingRequest} incomingRequest
	 * @param {number} nConnectionID
	 * @param {Object} objRTCIceCandidate
	 *
	 * @returns {null}
	 */
	async webRTCAddIceCandidate(incomingRequest, nRTCConnectionID, objRTCIceCandidate)
	{
		if(!this._objHookedUpRTCPairs[nRTCConnectionID])
		{
			throw new Error("Could not find connection ID " + JSON.stringify(nRTCConnectionID));
		}

		try
		{
			if(incomingRequest.connectionID === this._objHookedUpRTCPairs[nRTCConnectionID].objMale.nWebSocketConnectionID)
			{
				await this._objHookedUpRTCPairs[nRTCConnectionID].objFemale.client.rpc("webRTCAddIceCandidate", [nRTCConnectionID, objRTCIceCandidate]);
			}
			else if(incomingRequest.connectionID === this._objHookedUpRTCPairs[nRTCConnectionID].objFemale.nWebSocketConnectionID)
			{
				await this._objHookedUpRTCPairs[nRTCConnectionID].objMale.client.rpc("webRTCAddIceCandidate", [nRTCConnectionID, objRTCIceCandidate]);
			}
			else
			{
				throw new JSONRPC.Exception("You are not authorized to access connection ID " + nRTCConnectionID, JSONRPC.Exception.NOT_AUTHORIZED);
			}
		}
		catch(error)
		{
			this._objHookedUpRTCPairs[nRTCConnectionID].objMale.fnReject(error);
			this._objHookedUpRTCPairs[nRTCConnectionID].objFemale.fnReject(error);

			throw error;
		}
	}

	/**
	 * @param {JSONRPC.IncomingRequest} incomingRequest
	 * @param {number} nRTCConnectionID
	 *
	 * @returns {null}
	 */
	async femaleDataChannelIsOpen(incomingRequest, nRTCConnectionID)
	{
		if(incomingRequest.connectionID === this._objHookedUpRTCPairs[nRTCConnectionID].objFemale.nWebSocketConnectionID)
		{
			try
			{
				await this._objHookedUpRTCPairs[nRTCConnectionID].objMale.client.rpc("femaleDataChannelIsOpen", [nRTCConnectionID]);
			}
			catch(error)
			{
				this._objHookedUpRTCPairs[nRTCConnectionID].objMale.fnReject(error);
				this._objHookedUpRTCPairs[nRTCConnectionID].objFemale.fnReject(error);

				throw error;
			}

			this._objHookedUpRTCPairs[nRTCConnectionID].objMale.fnResolve(nRTCConnectionID);
			this._objHookedUpRTCPairs[nRTCConnectionID].objFemale.fnResolve(nRTCConnectionID);
		}
		else
		{
			const error = new Error("Unknown connection ID, or you are not authorized to signal the channel is open.");

			this._objHookedUpRTCPairs[nRTCConnectionID].objMale.fnReject(error);
			this._objHookedUpRTCPairs[nRTCConnectionID].objFemale.fnReject(error);

			throw error;
		}
	}
};
<|endoftext|>


<commit_before>/*
 * This is a hack to prevent inheritDoc and override tags from entirely removing
 * documentation of the method that inherits the documentation.
 *
 * TODO: Remove this hack when https://github.com/jsdoc3/jsdoc/issues/53
 * is addressed.
 */
exports.nodeVisitor = {

  visitNode: function(node, e, parser, currentSourceName) {
    if (/@(inheritDoc|override)(\n|\r)/.test(e.comment)) {
      e.preventDefault = true;
    }
  }

};<commit_msg>Fix @override documentation<commit_after>/*
 * This is a hack to prevent inheritDoc tags from entirely removing
 * documentation of the method that inherits the documentation.
 *
 * TODO: Remove this hack when https://github.com/jsdoc3/jsdoc/issues/53
 * is addressed.
 */
exports.nodeVisitor = {

  visitNode: function(node, e, parser, currentSourceName) {
    if (/@(inheritDoc)(\n|\r)/.test(e.comment)) {
      e.preventDefault = true;
    }
  }

};<|endoftext|>