@robotical/webapp-types 3.18.3 → 3.18.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,6 +18,7 @@ export default class RAFT implements RICInterface {
18
18
  systemInfo: RaftSystemInfo | null;
19
19
  raftStateInfo: RICStateInfo | SimplifiedCogStateInfo | null;
20
20
  isVerified: boolean;
21
+ private _verificationDecision;
21
22
  hasVerifiedConnection(): boolean;
22
23
  /**
23
24
  * Method to get ledLcdColours, to be implemented in child classes.
@@ -58,6 +58,7 @@ var RAFT = /** @class */ (function () {
58
58
  // RAFT State Info
59
59
  this.raftStateInfo = null;
60
60
  this.isVerified = false;
61
+ this._verificationDecision = null;
61
62
  }
62
63
  RAFT.prototype.hasVerifiedConnection = function () {
63
64
  return this.isVerified;
@@ -145,6 +146,7 @@ var RAFT = /** @class */ (function () {
145
146
  RAFT.prototype.verifyRaftPhoneApp = function (discoveredDevice) {
146
147
  return __awaiter(this, void 0, void 0, function () {
147
148
  return __generator(this, function (_a) {
149
+ this._verificationDecision = null;
148
150
  return [2 /*return*/, window.wrapperCommunicator.sendMessageAndWait(AppSentMessage.RAFT_VERIFY_PHONE, { ledLcdColours: this.ledLcdColours, discoveredDevice: discoveredDevice, raftId: this.id })];
149
151
  });
150
152
  });
@@ -155,6 +157,7 @@ var RAFT = /** @class */ (function () {
155
157
  RAFT.prototype.verifyRaft = function () {
156
158
  return __awaiter(this, void 0, void 0, function () {
157
159
  return __generator(this, function (_a) {
160
+ this._verificationDecision = null;
158
161
  return [2 /*return*/, window.wrapperCommunicator.sendMessageAndWait(AppSentMessage.RAFT_VERIFY, { ledLcdColours: this.ledLcdColours, raftId: this.id })];
159
162
  });
160
163
  });
@@ -163,11 +166,12 @@ var RAFT = /** @class */ (function () {
163
166
  * Stops the verification process
164
167
  */
165
168
  RAFT.prototype.stopVerifyingRaft = function (isCorrectRIC) {
166
- return __awaiter(this, void 0, void 0, function () {
167
- return __generator(this, function (_a) {
168
- return [2 /*return*/, window.wrapperCommunicator.sendMessageAndWait(AppSentMessage.RAFT_STOP_VERIFY, { isCorrectRIC: isCorrectRIC, raftId: this.id })];
169
- });
170
- });
169
+ if (this._verificationDecision) {
170
+ return this._verificationDecision.promise;
171
+ }
172
+ var promise = window.wrapperCommunicator.sendMessageAndWait(AppSentMessage.RAFT_STOP_VERIFY, { isCorrectRIC: isCorrectRIC, raftId: this.id });
173
+ this._verificationDecision = { isCorrectRIC: isCorrectRIC, promise: promise };
174
+ return promise;
171
175
  };
172
176
  /**
173
177
  * Receiveds events from the RICConnector
@@ -0,0 +1,11 @@
1
+ export type CameraStreamLifecycleLease = {
2
+ owner?: symbol;
3
+ release: () => void;
4
+ };
5
+ export declare const addCameraStreamLifecycleOwner: (constraints: MediaTrackConstraints | undefined, owner: symbol) => MediaTrackConstraints;
6
+ /**
7
+ * Keeps each camera request owned by the QR scanner that initiated it.
8
+ * getUserMedia cannot be aborted, so a stream that resolves after its scanner
9
+ * unmounts is stopped before the dependency's fixed startup delay can retain it.
10
+ */
11
+ export declare const acquireCameraStreamLifecycleGuard: () => CameraStreamLifecycleLease;
@@ -0,0 +1,202 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var CAMERA_STREAM_OWNER = Symbol("qr-camera-stream-owner");
13
+ var activeGuard;
14
+ var stopStreamOnce = function (stream) {
15
+ stream.getTracks().forEach(function (track) {
16
+ if (track.readyState === "ended") {
17
+ return;
18
+ }
19
+ var originalStop = track.stop.bind(track);
20
+ var stopped = false;
21
+ var stopOnce = function () {
22
+ if (stopped || track.readyState === "ended") {
23
+ stopped = true;
24
+ return;
25
+ }
26
+ stopped = true;
27
+ originalStop();
28
+ };
29
+ try {
30
+ Object.defineProperty(track, "stop", {
31
+ configurable: true,
32
+ writable: true,
33
+ value: stopOnce,
34
+ });
35
+ }
36
+ catch (_a) {
37
+ // Native MediaStreamTrack objects are normally extensible. If a WebView
38
+ // makes one read-only, stopping it is still more important than deduping
39
+ // a later, harmless stop call from the scanner dependency.
40
+ }
41
+ stopOnce();
42
+ });
43
+ };
44
+ var getOwnedRequest = function (constraints) {
45
+ var videoConstraints = constraints === null || constraints === void 0 ? void 0 : constraints.video;
46
+ if (!videoConstraints || typeof videoConstraints !== "object") {
47
+ return null;
48
+ }
49
+ var owner = videoConstraints[CAMERA_STREAM_OWNER];
50
+ if (!owner) {
51
+ return null;
52
+ }
53
+ var nativeVideoConstraints = __assign({}, videoConstraints);
54
+ Reflect.deleteProperty(nativeVideoConstraints, CAMERA_STREAM_OWNER);
55
+ return {
56
+ owner: owner,
57
+ constraints: __assign(__assign({}, constraints), { video: nativeVideoConstraints }),
58
+ };
59
+ };
60
+ var installGuard = function (mediaDevices) {
61
+ var _a, _b, _c;
62
+ var originalGetUserMedia = mediaDevices.getUserMedia;
63
+ if (typeof originalGetUserMedia !== "function") {
64
+ return null;
65
+ }
66
+ var state = {
67
+ mediaDevices: mediaDevices,
68
+ originalDescriptor: Object.getOwnPropertyDescriptor(mediaDevices, "getUserMedia"),
69
+ originalGetUserMedia: originalGetUserMedia,
70
+ guardedGetUserMedia: undefined,
71
+ owners: new Set(),
72
+ requests: new Set(),
73
+ };
74
+ state.guardedGetUserMedia = function (constraints) {
75
+ var ownedRequest = getOwnedRequest(constraints);
76
+ if (!ownedRequest) {
77
+ return state.originalGetUserMedia.call(state.mediaDevices, constraints);
78
+ }
79
+ var request = {
80
+ owner: ownedRequest.owner,
81
+ cancelled: false,
82
+ };
83
+ state.requests.add(request);
84
+ var cameraRequest;
85
+ try {
86
+ cameraRequest = state.originalGetUserMedia.call(state.mediaDevices, ownedRequest.constraints);
87
+ }
88
+ catch (error) {
89
+ state.requests.delete(request);
90
+ return Promise.reject(error);
91
+ }
92
+ return cameraRequest.then(function (stream) {
93
+ request.stream = stream;
94
+ if (request.cancelled || !state.owners.has(request.owner)) {
95
+ stopStreamOnce(stream);
96
+ state.requests.delete(request);
97
+ }
98
+ // Resolve the now-ended stream instead of rejecting. The scanner is
99
+ // already unmounted, and this lets its queued cleanup settle without
100
+ // reporting an expected AbortError to the React Native native logger.
101
+ return stream;
102
+ }, function (error) {
103
+ state.requests.delete(request);
104
+ throw error;
105
+ });
106
+ };
107
+ var installed = false;
108
+ try {
109
+ installed =
110
+ Reflect.set(mediaDevices, "getUserMedia", state.guardedGetUserMedia) &&
111
+ mediaDevices.getUserMedia === state.guardedGetUserMedia;
112
+ }
113
+ catch (_d) {
114
+ installed = false;
115
+ }
116
+ if (!installed && ((_a = state.originalDescriptor) === null || _a === void 0 ? void 0 : _a.configurable) !== false) {
117
+ try {
118
+ Object.defineProperty(mediaDevices, "getUserMedia", {
119
+ configurable: true,
120
+ enumerable: (_c = (_b = state.originalDescriptor) === null || _b === void 0 ? void 0 : _b.enumerable) !== null && _c !== void 0 ? _c : false,
121
+ writable: true,
122
+ value: state.guardedGetUserMedia,
123
+ });
124
+ installed = mediaDevices.getUserMedia === state.guardedGetUserMedia;
125
+ }
126
+ catch (_e) {
127
+ installed = false;
128
+ }
129
+ }
130
+ return installed ? state : null;
131
+ };
132
+ var restoreGetUserMedia = function (state) {
133
+ if (state.mediaDevices.getUserMedia !== state.guardedGetUserMedia) {
134
+ return true;
135
+ }
136
+ try {
137
+ if (state.originalDescriptor) {
138
+ Object.defineProperty(state.mediaDevices, "getUserMedia", state.originalDescriptor);
139
+ }
140
+ else {
141
+ Reflect.deleteProperty(state.mediaDevices, "getUserMedia");
142
+ }
143
+ }
144
+ catch (_a) {
145
+ return false;
146
+ }
147
+ return state.mediaDevices.getUserMedia !== state.guardedGetUserMedia;
148
+ };
149
+ export var addCameraStreamLifecycleOwner = function (constraints, owner) {
150
+ var _a;
151
+ return (__assign(__assign({}, constraints), (_a = {}, _a[CAMERA_STREAM_OWNER] = owner, _a)));
152
+ };
153
+ /**
154
+ * Keeps each camera request owned by the QR scanner that initiated it.
155
+ * getUserMedia cannot be aborted, so a stream that resolves after its scanner
156
+ * unmounts is stopped before the dependency's fixed startup delay can retain it.
157
+ */
158
+ export var acquireCameraStreamLifecycleGuard = function () {
159
+ var mediaDevices = typeof navigator === "undefined" ? undefined : navigator.mediaDevices;
160
+ if (!mediaDevices || typeof mediaDevices.getUserMedia !== "function") {
161
+ return { release: function () { return undefined; } };
162
+ }
163
+ var state = activeGuard;
164
+ if (!state ||
165
+ state.mediaDevices !== mediaDevices ||
166
+ mediaDevices.getUserMedia !== state.guardedGetUserMedia) {
167
+ state = installGuard(mediaDevices) || undefined;
168
+ if (!state) {
169
+ return { release: function () { return undefined; } };
170
+ }
171
+ activeGuard = state;
172
+ }
173
+ var acquiredState = state;
174
+ var owner = Symbol("qr-camera-owner");
175
+ acquiredState.owners.add(owner);
176
+ var released = false;
177
+ return {
178
+ owner: owner,
179
+ release: function () {
180
+ if (released) {
181
+ return;
182
+ }
183
+ released = true;
184
+ acquiredState.owners.delete(owner);
185
+ acquiredState.requests.forEach(function (request) {
186
+ if (request.owner !== owner) {
187
+ return;
188
+ }
189
+ request.cancelled = true;
190
+ if (request.stream) {
191
+ stopStreamOnce(request.stream);
192
+ acquiredState.requests.delete(request);
193
+ }
194
+ });
195
+ if (acquiredState.owners.size === 0 &&
196
+ restoreGetUserMedia(acquiredState) &&
197
+ activeGuard === acquiredState) {
198
+ activeGuard = undefined;
199
+ }
200
+ },
201
+ };
202
+ };
@@ -21,12 +21,27 @@ var __rest = (this && this.__rest) || function (s, e) {
21
21
  return t;
22
22
  };
23
23
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
24
- import { useMemo, useState } from "react";
24
+ import { useEffect, useMemo, useState } from "react";
25
25
  import { useTranslation } from "react-i18next";
26
26
  import { MdCameraswitch } from "react-icons/md";
27
27
  import { Scanner } from "@yudiel/react-qr-scanner";
28
28
  import isPhoneApp from "../../../utils/phone-app-communication/is-phone-app";
29
+ import { acquireCameraStreamLifecycleGuard, addCameraStreamLifecycleOwner, } from "./camera-stream-lifecycle-guard";
29
30
  import styles from "./styles.module.css";
31
+ function LifecycleGuardedScanner(props) {
32
+ var _a = useState(null), cameraLease = _a[0], setCameraLease = _a[1];
33
+ useEffect(function () {
34
+ var lease = acquireCameraStreamLifecycleGuard();
35
+ setCameraLease(lease);
36
+ return lease.release;
37
+ }, []);
38
+ var guardedConstraints = useMemo(function () {
39
+ return (cameraLease === null || cameraLease === void 0 ? void 0 : cameraLease.owner)
40
+ ? addCameraStreamLifecycleOwner(props.constraints, cameraLease.owner)
41
+ : props.constraints;
42
+ }, [cameraLease, props.constraints]);
43
+ return cameraLease ? (_jsx(Scanner, __assign({}, props, { constraints: guardedConstraints }))) : null;
44
+ }
30
45
  export default function SwitchableCameraScanner(_a) {
31
46
  var constraints = _a.constraints, scannerProps = __rest(_a, ["constraints"]);
32
47
  var t = useTranslation().t;
@@ -39,7 +54,7 @@ export default function SwitchableCameraScanner(_a) {
39
54
  var cameraSwitchLabel = switchToFrontCamera
40
55
  ? t("connection.switch_to_front_camera")
41
56
  : t("connection.switch_to_rear_camera");
42
- return (_jsxs("div", __assign({ className: styles.container }, { children: [_jsx(Scanner, __assign({}, scannerProps, { constraints: cameraConstraints })), isPhoneApp() && (_jsx("button", __assign({ type: "button", className: styles.cameraSwitchButton, "aria-label": cameraSwitchLabel, title: cameraSwitchLabel, onClick: function () {
57
+ return (_jsxs("div", __assign({ className: styles.container }, { children: [_jsx(LifecycleGuardedScanner, __assign({}, scannerProps, { constraints: cameraConstraints })), isPhoneApp() && (_jsx("button", __assign({ type: "button", className: styles.cameraSwitchButton, "aria-label": cameraSwitchLabel, title: cameraSwitchLabel, onClick: function () {
43
58
  return setFacingMode(function (currentFacingMode) {
44
59
  return currentFacingMode === "environment" ? "user" : "environment";
45
60
  });
@@ -110,7 +110,11 @@ export default function LEDLightsOrQRVerificationModal(_a) {
110
110
  return [2 /*return*/];
111
111
  });
112
112
  }); };
113
- return (_jsxs("div", __assign({ className: styles.container }, { children: [_jsxs("div", __assign({ className: styles.robotContext }, { children: [_jsx("p", __assign({ className: styles.robotContextEyebrow }, { children: t("connection.connecting_to", "Connecting to") })), _jsx("h3", __assign({ className: styles.robotContextTitle }, { children: targetRaftType })), allowSwitchToMarty && (_jsx("button", __assign({ type: "button", className: styles.switchRobotButton, onClick: function () { return modalState.closeModal({ action: "switch-to-marty" }); } }, { children: t("connection.connect_marty_instead", "Connect Marty instead") })))] })), _jsxs("div", __assign({ className: styles.bluetoothNotice }, { children: [_jsx(BluetoothSVG, { className: styles.bluetoothIcon, fill: MAIN_BLUE, "aria-hidden": "true", focusable: "false" }), _jsx("span", __assign({ className: styles.bluetoothText }, { children: t("configuration_screen.bluetooth") }))] })), _jsxs("div", { children: [_jsx("p", __assign({ className: styles.robotContextSubtitle }, { children: t("connection.cog_verification_prompt", "Choose how you want to verify your Cog connection.") })), !isButtonToConnectVisible ? _jsx("div", __assign({ className: styles.cards }, { children: options.map(function (_a) {
113
+ return (_jsxs("div", __assign({ className: styles.container }, { children: [_jsxs("div", __assign({ className: styles.robotContext }, { children: [_jsx("p", __assign({ className: styles.robotContextEyebrow }, { children: t("connection.connecting_to", "Connecting to") })), _jsx("h3", __assign({ className: styles.robotContextTitle }, { children: targetRaftType })), allowSwitchToMarty && (_jsx("button", __assign({ type: "button", className: styles.switchRobotButton, onClick: function () {
114
+ return modalState.closeModalWithoutAnimation({
115
+ action: "switch-to-marty",
116
+ });
117
+ } }, { children: t("connection.connect_marty_instead", "Connect Marty instead") })))] })), _jsxs("div", __assign({ className: styles.bluetoothNotice }, { children: [_jsx(BluetoothSVG, { className: styles.bluetoothIcon, fill: MAIN_BLUE, "aria-hidden": "true", focusable: "false" }), _jsx("span", __assign({ className: styles.bluetoothText }, { children: t("configuration_screen.bluetooth") }))] })), _jsxs("div", { children: [_jsx("p", __assign({ className: styles.robotContextSubtitle }, { children: t("connection.cog_verification_prompt", "Choose how you want to verify your Cog connection.") })), !isButtonToConnectVisible ? _jsx("div", __assign({ className: styles.cards }, { children: options.map(function (_a) {
114
118
  var id = _a.id, title = _a.title, subtitle = _a.subtitle, icon = _a.icon;
115
119
  return (_jsxs("button", __assign({ type: "button", className: qrSelected && id === 'qr' ? [styles.card, styles.qrselected].join(" ") : styles.card, onClick: function () { return handleSelect(id); }, "aria-label": title, "aria-pressed": id === "qr" ? qrSelected : false }, { children: [_jsx("div", __assign({ className: styles.icon }, { children: icon })), _jsx("h3", __assign({ className: styles.title }, { children: title })), _jsx("p", __assign({ className: styles.subtitle }, { children: subtitle }))] }), id));
116
120
  }) })) :
@@ -46,7 +46,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
46
46
  }
47
47
  };
48
48
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
49
- import { useEffect, useState } from "react";
49
+ import { useEffect, useRef, useState } from "react";
50
50
  import "./styles.css";
51
51
  import { ReactComponent as BluetoothSVG } from "../../../assets/configuration/bluetooth.svg";
52
52
  import { MAIN_BLUE } from "../../../styles/colors";
@@ -62,7 +62,16 @@ function VerificationModal(_a) {
62
62
  var _b = useState([]), randomColours = _b[0], setRandomColours = _b[1];
63
63
  // const [connectedRaft, setConnectedRaft] = useState<RAFT | undefined>(connectedRAFT_);
64
64
  var _c = useState(-200), RSSI = _c[0], setRSSI = _c[1];
65
+ var _d = useState(false), isActionPending = _d[0], setIsActionPending = _d[1];
66
+ var actionStartedRef = useRef(false);
65
67
  var t = useTranslation().t;
68
+ var claimVerificationAction = function () {
69
+ if (actionStartedRef.current)
70
+ return false;
71
+ actionStartedRef.current = true;
72
+ setIsActionPending(true);
73
+ return true;
74
+ };
66
75
  useEffect(function () {
67
76
  /* Subscribe to the connectedRaft's SIGNAL strength */
68
77
  if (!connectedRAFT_)
@@ -76,6 +85,8 @@ function VerificationModal(_a) {
76
85
  return __generator(this, function (_a) {
77
86
  switch (_a.label) {
78
87
  case 0:
88
+ if (!claimVerificationAction())
89
+ return [2 /*return*/];
79
90
  // close modal, stop verification process, disconnect, scan again
80
91
  modalState.closeModal();
81
92
  if (!connectedRAFT_)
@@ -91,6 +102,8 @@ function VerificationModal(_a) {
91
102
  return __generator(this, function (_a) {
92
103
  switch (_a.label) {
93
104
  case 0:
105
+ if (!claimVerificationAction())
106
+ return [2 /*return*/];
94
107
  modalState.closeModal();
95
108
  return [4 /*yield*/, (connectedRAFT_ === null || connectedRAFT_ === void 0 ? void 0 : connectedRAFT_.stopVerifyingRaft(true))];
96
109
  case 1:
@@ -113,7 +126,7 @@ function VerificationModal(_a) {
113
126
  /* Verify the connectedRaft once it's connected */
114
127
  connectedRAFT_ === null || connectedRAFT_ === void 0 ? void 0 : connectedRAFT_.verifyRaft();
115
128
  }, [connectedRAFT_]);
116
- var contentJSX = _jsxs("div", __assign({ className: "verification-modal-container" }, { children: [_jsxs("div", __assign({ className: "verification-modal-martys-container" }, { children: [_jsxs("div", __assign({ className: "verification-modal-marty-name-row-container" }, { children: [_jsx("p", __assign({ className: "verification-modal-marty-name" }, { children: connectedRAFT_ === null || connectedRAFT_ === void 0 ? void 0 : connectedRAFT_.getFriendlyName() })), _jsx("div", __assign({ className: "verification-modal-marty-signal-container" }, { children: _jsx(RICSignal, { signalStrength: RSSI }) })), _jsx(BluetoothSVG, { fill: MAIN_BLUE })] })), _jsx("p", __assign({ className: "verification-modal-martys-back-hint" }, { children: t("verification_modal_phone.look_on_back", { raftType: connectedRAFT_ === null || connectedRAFT_ === void 0 ? void 0 : connectedRAFT_.type }) })), _jsxs("div", __assign({ className: "verification-modal-led-row-container" }, { children: [_jsx(LEDs, { coloursArr: randomColours, connectedRaft: connectedRAFT_ }), _jsx(SimpleButton, { onClick: onCancel, title: t("verification_modal_phone.no"), borderColour: "red", colour: "white", textColour: "red" }), _jsx(SimpleButton, { onClick: onYes, title: t("connection.yes") })] }))] })), _jsxs("div", __assign({ className: "verification-modal-bottom-btns-container" }, { children: [_jsx("div", { className: "dummy-gap" }), _jsx(SimpleButton, { onClick: onCancel, title: t("connection.cancel") })] }))] }));
129
+ var contentJSX = _jsxs("div", __assign({ className: "verification-modal-container" }, { children: [_jsxs("div", __assign({ className: "verification-modal-martys-container" }, { children: [_jsxs("div", __assign({ className: "verification-modal-marty-name-row-container" }, { children: [_jsx("p", __assign({ className: "verification-modal-marty-name" }, { children: connectedRAFT_ === null || connectedRAFT_ === void 0 ? void 0 : connectedRAFT_.getFriendlyName() })), _jsx("div", __assign({ className: "verification-modal-marty-signal-container" }, { children: _jsx(RICSignal, { signalStrength: RSSI }) })), _jsx(BluetoothSVG, { fill: MAIN_BLUE })] })), _jsx("p", __assign({ className: "verification-modal-martys-back-hint" }, { children: t("verification_modal_phone.look_on_back", { raftType: connectedRAFT_ === null || connectedRAFT_ === void 0 ? void 0 : connectedRAFT_.type }) })), _jsxs("div", __assign({ className: "verification-modal-led-row-container" }, { children: [_jsx(LEDs, { coloursArr: randomColours, connectedRaft: connectedRAFT_ }), _jsx(SimpleButton, { onClick: onCancel, title: t("verification_modal_phone.no"), disabled: isActionPending, borderColour: "red", colour: "white", textColour: "red" }), _jsx(SimpleButton, { onClick: onYes, title: t("connection.yes"), disabled: isActionPending })] }))] })), _jsxs("div", __assign({ className: "verification-modal-bottom-btns-container" }, { children: [_jsx("div", { className: "dummy-gap" }), _jsx(SimpleButton, { onClick: onCancel, title: t("connection.cancel"), disabled: isActionPending })] }))] }));
117
130
  return contentJSX;
118
131
  }
119
132
  export default VerificationModal;
@@ -55,7 +55,7 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
55
55
  return to.concat(ar || Array.prototype.slice.call(from));
56
56
  };
57
57
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
58
- import { Fragment, useEffect, useState } from "react";
58
+ import { Fragment, useEffect, useRef, useState } from "react";
59
59
  import "./styles.css";
60
60
  import { ReactComponent as BluetoothSVG } from "../../../assets/configuration/bluetooth.svg";
61
61
  import { ReactComponent as RefreshSVG } from "../../../assets/refresh.svg";
@@ -81,6 +81,15 @@ function VerificationModalPhoneApp(_a) {
81
81
  var _e = useState(false), isLoading = _e[0], setIsLoading = _e[1];
82
82
  var _f = useState(isPhoneApp()), isScanning = _f[0], setIsScanning = _f[1];
83
83
  var _g = useState(), connectedRaft = _g[0], setConnectedRaft = _g[1];
84
+ var _h = useState(false), isActionPending = _h[0], setIsActionPending = _h[1];
85
+ var actionStartedRef = useRef(false);
86
+ var claimVerificationAction = function () {
87
+ if (actionStartedRef.current)
88
+ return false;
89
+ actionStartedRef.current = true;
90
+ setIsActionPending(true);
91
+ return true;
92
+ };
84
93
  useEffect(function () {
85
94
  raftDiscoveryFinishedSubscriptionHelper().subscribe(function () { return setIsScanning(false); });
86
95
  raftDiscoveryStartedSubscriptionHelper().subscribe(function () { return setIsScanning(true); });
@@ -108,6 +117,8 @@ function VerificationModalPhoneApp(_a) {
108
117
  return __generator(this, function (_a) {
109
118
  switch (_a.label) {
110
119
  case 0:
120
+ if (!claimVerificationAction())
121
+ return [2 /*return*/];
111
122
  if (!connectedRaft) return [3 /*break*/, 2];
112
123
  return [4 /*yield*/, window.applicationManager.stopVerifyingRaft(connectedRaft.id, false)];
113
124
  case 1:
@@ -128,6 +139,8 @@ function VerificationModalPhoneApp(_a) {
128
139
  return __generator(this, function (_a) {
129
140
  switch (_a.label) {
130
141
  case 0:
142
+ if (!claimVerificationAction())
143
+ return [2 /*return*/];
131
144
  modalState.closeModalWithoutAnimation();
132
145
  if (!connectedRaft) return [3 /*break*/, 2];
133
146
  return [4 /*yield*/, window.applicationManager.stopVerifyingRaft(connectedRaft.id, false)];
@@ -150,6 +163,8 @@ function VerificationModalPhoneApp(_a) {
150
163
  return __generator(this, function (_a) {
151
164
  switch (_a.label) {
152
165
  case 0:
166
+ if (!claimVerificationAction())
167
+ return [2 /*return*/];
153
168
  modalState.closeModal();
154
169
  return [4 /*yield*/, (connectedRaft === null || connectedRaft === void 0 ? void 0 : connectedRaft.stopVerifyingRaft(true))];
155
170
  case 1:
@@ -174,6 +189,8 @@ function VerificationModalPhoneApp(_a) {
174
189
  return __generator(this, function (_a) {
175
190
  switch (_a.label) {
176
191
  case 0:
192
+ if (actionStartedRef.current)
193
+ return [2 /*return*/];
177
194
  setIsLoading(true);
178
195
  if (!connectedRaft) return [3 /*break*/, 2];
179
196
  return [4 /*yield*/, window.applicationManager.stopVerifyingRaft(connectedRaft.id, false)];
@@ -200,12 +217,12 @@ function VerificationModalPhoneApp(_a) {
200
217
  }); };
201
218
  var contentJSX = null;
202
219
  if (foundRICsState.length === 0) {
203
- contentJSX = _jsxs("div", __assign({ className: "verification-modal-container" }, { children: [!isScanning && _jsx("p", __assign({ style: { alignSelf: 'center' } }, { children: t("verification_modal_phone.no_robots_found", "No devices found") })), _jsx("div", __assign({ className: "verification-modal-martys-container" }, { children: isScanning && _jsxs("div", __assign({ style: { margin: "auto", width: "50px", height: "50px" } }, { children: [" ", _jsx(LoadingSpinner, {}), " "] })) })), _jsxs("div", __assign({ className: "verification-modal-bottom-btns-container" }, { children: [_jsx(SVGImageButton, { onClick: onScanAgain, title: t("verification_modal_phone.scan_again", "SCAN AGAIN"), SVGImage: RefreshSVG, backgroundColour: MAIN_BLUE, titleColour: WHITE, SVGColour: WHITE }), _jsx("div", { className: "dummy-gap" }), _jsx(SimpleButton, { onClick: onCancel, title: t("verification_modal_phone.cancel", "CANCEL") })] }))] }));
220
+ contentJSX = _jsxs("div", __assign({ className: "verification-modal-container" }, { children: [!isScanning && _jsx("p", __assign({ style: { alignSelf: 'center' } }, { children: t("verification_modal_phone.no_robots_found", "No devices found") })), _jsx("div", __assign({ className: "verification-modal-martys-container" }, { children: isScanning && _jsxs("div", __assign({ style: { margin: "auto", width: "50px", height: "50px" } }, { children: [" ", _jsx(LoadingSpinner, {}), " "] })) })), _jsxs("div", __assign({ className: "verification-modal-bottom-btns-container" }, { children: [_jsx(SVGImageButton, { onClick: onScanAgain, title: t("verification_modal_phone.scan_again", "SCAN AGAIN"), SVGImage: RefreshSVG, backgroundColour: MAIN_BLUE, titleColour: WHITE, SVGColour: WHITE, disabled: isScanning || isActionPending }), _jsx("div", { className: "dummy-gap" }), _jsx(SimpleButton, { onClick: onCancel, title: t("verification_modal_phone.cancel", "CANCEL"), disabled: isActionPending })] }))] }));
204
221
  }
205
222
  else {
206
223
  contentJSX = _jsxs("div", __assign({ className: "verification-modal-container" }, { children: [_jsxs("div", __assign({ className: "verification-modal-martys-container" }, { children: [isScanning && _jsxs("div", __assign({ style: { margin: "auto", width: "50px", height: "50px" } }, { children: [" ", _jsx(LoadingSpinner, {}), " "] })), foundRICsState.map(function (raftObj, raftIdx) {
207
- return _jsxs(Fragment, { children: [_jsxs("button", __assign({ type: "button", className: "verification-modal-marty-name-row-container", onClick: function () { return onSelectRaft(raftIdx); }, "aria-label": "".concat(t("verification_modal_phone.select_robot", "Select device"), " ").concat(raftObj._localName) }, { children: [_jsx("p", __assign({ className: "verification-modal-marty-name" }, { children: raftObj._localName })), _jsx("div", __assign({ className: "verification-modal-marty-signal-container" }, { children: _jsx(RICSignal, { signalStrength: raftObj._rssi }) })), _jsx(BluetoothSVG, { fill: MAIN_BLUE, "aria-hidden": "true" })] })), selectedRICIdx === raftIdx && _jsxs("div", { children: [_jsx("p", __assign({ className: "verification-modal-martys-back-hint" }, { children: isLoading ? t("verification_modal_phone.loading", "Loading...") : t("verification_modal_phone.look_on_back", { raftType: connectedRaft === null || connectedRaft === void 0 ? void 0 : connectedRaft.type }) })), _jsxs("div", __assign({ className: "verification-modal-led-row-container" }, { children: [_jsx(LEDs, { coloursArr: randomColours, connectedRaft: connectedRaft }), !isLoading && _jsx(SimpleButton, { onClick: onCancel, title: t("verification_modal_phone.no", "No"), borderColour: "red", colour: "white", textColour: "red" }), !isLoading && _jsx(SimpleButton, { onClick: onYes, title: t("connection.yes") })] }))] })] }, raftIdx);
208
- })] })), _jsxs("div", __assign({ className: "verification-modal-bottom-btns-container" }, { children: [_jsx(SVGImageButton, { onClick: onScanAgain, title: t("connection.scan_again"), SVGImage: RefreshSVG, backgroundColour: MAIN_BLUE, titleColour: WHITE, SVGColour: WHITE, disabled: isScanning }), _jsx("div", { className: "dummy-gap" }), _jsx(SimpleButton, { onClick: onCancel, title: t("connection.cancel") })] }))] }));
224
+ return _jsxs(Fragment, { children: [_jsxs("button", __assign({ type: "button", className: "verification-modal-marty-name-row-container", onClick: function () { return onSelectRaft(raftIdx); }, "aria-label": "".concat(t("verification_modal_phone.select_robot", "Select device"), " ").concat(raftObj._localName), disabled: isActionPending || isLoading }, { children: [_jsx("p", __assign({ className: "verification-modal-marty-name" }, { children: raftObj._localName })), _jsx("div", __assign({ className: "verification-modal-marty-signal-container" }, { children: _jsx(RICSignal, { signalStrength: raftObj._rssi }) })), _jsx(BluetoothSVG, { fill: MAIN_BLUE, "aria-hidden": "true" })] })), selectedRICIdx === raftIdx && _jsxs("div", { children: [_jsx("p", __assign({ className: "verification-modal-martys-back-hint" }, { children: isLoading ? t("verification_modal_phone.loading", "Loading...") : t("verification_modal_phone.look_on_back", { raftType: connectedRaft === null || connectedRaft === void 0 ? void 0 : connectedRaft.type }) })), _jsxs("div", __assign({ className: "verification-modal-led-row-container" }, { children: [_jsx(LEDs, { coloursArr: randomColours, connectedRaft: connectedRaft }), !isLoading && _jsx(SimpleButton, { onClick: onCancel, title: t("verification_modal_phone.no", "No"), disabled: isActionPending, borderColour: "red", colour: "white", textColour: "red" }), !isLoading && _jsx(SimpleButton, { onClick: onYes, title: t("connection.yes"), disabled: isActionPending })] }))] })] }, raftIdx);
225
+ })] })), _jsxs("div", __assign({ className: "verification-modal-bottom-btns-container" }, { children: [_jsx(SVGImageButton, { onClick: onScanAgain, title: t("connection.scan_again"), SVGImage: RefreshSVG, backgroundColour: MAIN_BLUE, titleColour: WHITE, SVGColour: WHITE, disabled: isScanning || isActionPending }), _jsx("div", { className: "dummy-gap" }), _jsx(SimpleButton, { onClick: onCancel, title: t("connection.cancel"), disabled: isActionPending })] }))] }));
209
226
  }
210
227
  return contentJSX;
211
228
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robotical/webapp-types",
3
- "version": "3.18.3",
3
+ "version": "3.18.5",
4
4
  "description": "Type definitions for the Application Manager",
5
5
  "main": "dist/application-manager.d.ts",
6
6
  "types": "dist/application-manager.d.ts",