@stoprocent/noble 1.19.0 → 2.0.0
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.
- package/README.md +393 -650
- package/examples/advertisement-discovery.js +57 -48
- package/examples/connect-address.js +59 -34
- package/examples/echo.js +59 -69
- package/examples/enter-exit.js +55 -49
- package/examples/multiple-bindings.js +53 -0
- package/examples/peripheral-explorer-async.js +39 -21
- package/examples/peripheral-explorer.ts +52 -0
- package/index.d.ts +249 -209
- package/index.js +4 -1
- package/jest.config.js +4 -0
- package/lib/characteristic.js +153 -127
- package/lib/{win/src/callbacks.h → common/include/Emit.h} +17 -14
- package/lib/common/include/Peripheral.h +31 -0
- package/lib/common/include/ThreadSafeCallback.h +95 -0
- package/lib/{win/src/callbacks.cc → common/src/Emit.cc} +111 -68
- package/lib/descriptor.js +57 -54
- package/lib/hci-socket/acl-stream.js +2 -4
- package/lib/hci-socket/bindings.js +96 -73
- package/lib/hci-socket/gap.js +2 -3
- package/lib/hci-socket/gatt.js +2 -5
- package/lib/hci-socket/hci.js +19 -7
- package/lib/hci-socket/signaling.js +2 -3
- package/lib/hci-socket/smp.js +2 -3
- package/lib/hci-socket/vs.js +1 -0
- package/lib/mac/binding.gyp +5 -7
- package/lib/mac/bindings.js +1 -3
- package/lib/mac/src/ble_manager.h +1 -8
- package/lib/mac/src/ble_manager.mm +87 -44
- package/lib/mac/src/napi_objc.h +1 -0
- package/lib/mac/src/napi_objc.mm +0 -6
- package/lib/mac/src/noble_mac.h +5 -3
- package/lib/mac/src/noble_mac.mm +99 -57
- package/lib/mac/src/objc_cpp.h +3 -2
- package/lib/mac/src/objc_cpp.mm +0 -6
- package/lib/noble.js +579 -488
- package/lib/peripheral.js +171 -174
- package/lib/resolve-bindings.js +37 -30
- package/lib/service.js +58 -55
- package/lib/win/binding.gyp +4 -11
- package/lib/win/bindings.js +1 -3
- package/lib/win/src/ble_manager.cc +291 -166
- package/lib/win/src/ble_manager.h +11 -13
- package/lib/win/src/napi_winrt.cc +1 -7
- package/lib/win/src/napi_winrt.h +1 -1
- package/lib/win/src/noble_winrt.cc +88 -61
- package/lib/win/src/noble_winrt.h +5 -3
- package/lib/win/src/notify_map.cc +0 -7
- package/lib/win/src/notify_map.h +1 -8
- package/lib/win/src/peripheral_winrt.cc +29 -11
- package/lib/win/src/peripheral_winrt.h +1 -1
- package/lib/win/src/radio_watcher.cc +79 -69
- package/lib/win/src/radio_watcher.h +30 -11
- package/lib/win/src/winrt_cpp.cc +1 -1
- package/lib/win/src/winrt_cpp.h +3 -0
- package/package.json +14 -17
- package/prebuilds/darwin-x64+arm64/@stoprocent+noble.node +0 -0
- package/prebuilds/win32-ia32/@stoprocent+noble.node +0 -0
- package/prebuilds/win32-x64/@stoprocent+noble.node +0 -0
- package/test/lib/characteristic.test.js +202 -322
- package/test/lib/descriptor.test.js +62 -95
- package/test/lib/hci-socket/acl-stream.test.js +112 -108
- package/test/lib/hci-socket/bindings.test.js +576 -365
- package/test/lib/hci-socket/hci.test.js +442 -473
- package/test/lib/hci-socket/signaling.test.js +45 -48
- package/test/lib/hci-socket/smp.test.js +144 -142
- package/test/lib/hci-socket/vs.test.js +193 -18
- package/test/lib/peripheral.test.js +492 -322
- package/test/lib/resolve-bindings.test.js +207 -82
- package/test/lib/service.test.js +79 -88
- package/test/noble.test.js +381 -1085
- package/.editorconfig +0 -11
- package/.nycrc.json +0 -4
- package/codecov.yml +0 -5
- package/examples/cache-gatt-discovery.js +0 -198
- package/examples/cache-gatt-reconnect.js +0 -164
- package/examples/ext-advertisement-discovery.js +0 -65
- package/examples/peripheral-explorer.js +0 -225
- package/examples/pizza/central.js +0 -194
- package/examples/pizza/pizza.js +0 -60
- package/examples/test/test.custom.js +0 -131
- package/examples/uart-bind-params.js +0 -28
- package/lib/distributed/bindings.js +0 -326
- package/lib/mac/src/callbacks.cc +0 -222
- package/lib/mac/src/callbacks.h +0 -84
- package/lib/mac/src/peripheral.h +0 -23
- package/lib/resolve-bindings-web.js +0 -9
- package/lib/webbluetooth/bindings.js +0 -368
- package/lib/websocket/bindings.js +0 -321
- package/lib/win/src/peripheral.h +0 -23
- package/test/lib/distributed/bindings.test.js +0 -918
- package/test/lib/webbluetooth/bindings.test.js +0 -190
- package/test/lib/websocket/bindings.test.js +0 -456
- package/test/mocha.setup.js +0 -0
- package/with-bindings.js +0 -5
- package/with-custom-binding.js +0 -6
- package/ws-slave.js +0 -404
package/lib/characteristic.js
CHANGED
|
@@ -1,161 +1,187 @@
|
|
|
1
|
-
const
|
|
2
|
-
const util = require('util');
|
|
1
|
+
const { EventEmitter } = require('events');
|
|
3
2
|
|
|
4
3
|
const characteristics = require('./characteristics.json');
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
5
|
+
class Characteristic extends EventEmitter {
|
|
6
|
+
|
|
7
|
+
constructor (noble, peripheralId, serviceUuid, uuid, properties) {
|
|
8
|
+
super();
|
|
9
|
+
this._noble = noble;
|
|
10
|
+
this._peripheralId = peripheralId;
|
|
11
|
+
this._serviceUuid = serviceUuid;
|
|
12
|
+
|
|
13
|
+
this.uuid = uuid;
|
|
14
|
+
this.name = null;
|
|
15
|
+
this.type = null;
|
|
16
|
+
this.properties = properties;
|
|
17
|
+
this.descriptors = null;
|
|
18
|
+
|
|
19
|
+
const characteristic = characteristics[uuid];
|
|
20
|
+
if (characteristic) {
|
|
21
|
+
this.name = characteristic.name;
|
|
22
|
+
this.type = characteristic.type;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
16
25
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
toString () {
|
|
27
|
+
return JSON.stringify({
|
|
28
|
+
uuid: this.uuid,
|
|
29
|
+
name: this.name,
|
|
30
|
+
type: this.type,
|
|
31
|
+
properties: this.properties
|
|
32
|
+
});
|
|
21
33
|
}
|
|
22
|
-
}
|
|
23
34
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
// only call the callback if 'read' event and non-notification
|
|
39
|
-
// 'read' for non-notifications is only present for backwards compatbility
|
|
40
|
-
if (!isNotification) {
|
|
41
|
-
// remove the listener
|
|
42
|
-
this.removeListener('read', onRead);
|
|
43
|
-
|
|
44
|
-
// call the callback
|
|
45
|
-
callback(null, data);
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
this.on('read', onRead);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
this._noble.read(
|
|
53
|
-
this._peripheralId,
|
|
54
|
-
this._serviceUuid,
|
|
55
|
-
this.uuid
|
|
56
|
-
);
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
Characteristic.prototype.read = read;
|
|
60
|
-
Characteristic.prototype.readAsync = util.promisify(read);
|
|
61
|
-
|
|
62
|
-
const write = function (data, withoutResponse, callback) {
|
|
63
|
-
if (process.title !== 'browser') {
|
|
64
|
-
const allowedTypes = [
|
|
65
|
-
Buffer,
|
|
66
|
-
Uint8Array,
|
|
67
|
-
Uint16Array,
|
|
68
|
-
Uint32Array
|
|
69
|
-
];
|
|
70
|
-
if (!allowedTypes.some((allowedType) => data instanceof allowedType)) {
|
|
71
|
-
throw new Error(`data must be a ${allowedTypes.map((allowedType) => allowedType.name).join(' or ')}`);
|
|
35
|
+
read (callback) {
|
|
36
|
+
if (callback) {
|
|
37
|
+
const onRead = (data, isNotification, error) => {
|
|
38
|
+
// only call the callback if 'read' event and non-notification
|
|
39
|
+
// 'read' for non-notifications is only present for backwards compatbility
|
|
40
|
+
if (!isNotification) {
|
|
41
|
+
// remove the listener
|
|
42
|
+
this.removeListener('read', onRead);
|
|
43
|
+
// call the callback
|
|
44
|
+
callback(error, data);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
this.on('read', onRead);
|
|
72
49
|
}
|
|
50
|
+
|
|
51
|
+
this._noble.read(
|
|
52
|
+
this._peripheralId,
|
|
53
|
+
this._serviceUuid,
|
|
54
|
+
this.uuid
|
|
55
|
+
);
|
|
73
56
|
}
|
|
74
57
|
|
|
75
|
-
|
|
76
|
-
this.
|
|
77
|
-
|
|
58
|
+
async readAsync () {
|
|
59
|
+
return this._noble._withDisconnectHandler(this._peripheralId, () => {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
this.read((error, data) => error ? reject(error) : resolve(data));
|
|
62
|
+
});
|
|
78
63
|
});
|
|
79
64
|
}
|
|
80
65
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
66
|
+
write (data, withoutResponse, callback) {
|
|
67
|
+
if (process.title !== 'browser') {
|
|
68
|
+
const allowedTypes = [
|
|
69
|
+
Buffer,
|
|
70
|
+
Uint8Array,
|
|
71
|
+
Uint16Array,
|
|
72
|
+
Uint32Array
|
|
73
|
+
];
|
|
74
|
+
if (!allowedTypes.some((allowedType) => data instanceof allowedType)) {
|
|
75
|
+
throw new Error(`data must be a ${allowedTypes.map((allowedType) => allowedType.name).join(' or ')}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
89
78
|
|
|
90
|
-
|
|
91
|
-
|
|
79
|
+
if (callback) {
|
|
80
|
+
this.once('write', error => callback(error));
|
|
81
|
+
}
|
|
92
82
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
83
|
+
this._noble.write(
|
|
84
|
+
this._peripheralId,
|
|
85
|
+
this._serviceUuid,
|
|
86
|
+
this.uuid,
|
|
87
|
+
data,
|
|
88
|
+
withoutResponse
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async writeAsync (data, withoutResponse) {
|
|
93
|
+
return this._noble._withDisconnectHandler(this._peripheralId, () => {
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
this.write(data, withoutResponse, error => error ? reject(error) : resolve());
|
|
96
|
+
});
|
|
97
97
|
});
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
broadcast
|
|
105
|
-
);
|
|
106
|
-
};
|
|
100
|
+
broadcast (broadcast, callback) {
|
|
101
|
+
if (callback) {
|
|
102
|
+
this.once('broadcast', error => callback(error));
|
|
103
|
+
}
|
|
107
104
|
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
this._noble.broadcast(
|
|
106
|
+
this._peripheralId,
|
|
107
|
+
this._serviceUuid,
|
|
108
|
+
this.uuid,
|
|
109
|
+
broadcast
|
|
110
|
+
);
|
|
111
|
+
}
|
|
110
112
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
async broadcastAsync (broadcast) {
|
|
114
|
+
return this._noble._withDisconnectHandler(this._peripheralId, () => {
|
|
115
|
+
return new Promise((resolve, reject) => {
|
|
116
|
+
this.broadcast(broadcast, (state, error) => error ? reject(error) : resolve(state));
|
|
117
|
+
});
|
|
116
118
|
});
|
|
117
119
|
}
|
|
118
120
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
notify
|
|
124
|
-
);
|
|
125
|
-
};
|
|
121
|
+
notify (notify, callback) {
|
|
122
|
+
if (callback) {
|
|
123
|
+
this.once('notify', (state, error) => callback(error, state));
|
|
124
|
+
}
|
|
126
125
|
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
this._noble.notify(
|
|
127
|
+
this._peripheralId,
|
|
128
|
+
this._serviceUuid,
|
|
129
|
+
this.uuid,
|
|
130
|
+
notify
|
|
131
|
+
);
|
|
132
|
+
}
|
|
129
133
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
134
|
+
async notifyAsync (notify) {
|
|
135
|
+
return this._noble._withDisconnectHandler(this._peripheralId, () => {
|
|
136
|
+
return new Promise((resolve, reject) => {
|
|
137
|
+
this.notify(notify, (error, state) => error ? reject(error) : resolve(state));
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
133
141
|
|
|
134
|
-
|
|
135
|
-
|
|
142
|
+
subscribe (callback) {
|
|
143
|
+
this.notify(true, callback);
|
|
144
|
+
}
|
|
136
145
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
146
|
+
async subscribeAsync () {
|
|
147
|
+
return this._noble._withDisconnectHandler(this._peripheralId, () => {
|
|
148
|
+
return new Promise((resolve, reject) => {
|
|
149
|
+
this.subscribe((error, state) => error ? reject(error) : resolve(state));
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
140
153
|
|
|
141
|
-
|
|
142
|
-
|
|
154
|
+
unsubscribe (callback) {
|
|
155
|
+
this.notify(false, callback);
|
|
156
|
+
}
|
|
143
157
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
158
|
+
async unsubscribeAsync () {
|
|
159
|
+
return this._noble._withDisconnectHandler(this._peripheralId, () => {
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
161
|
+
this.unsubscribe(error => error ? reject(error) : resolve());
|
|
162
|
+
});
|
|
148
163
|
});
|
|
149
164
|
}
|
|
150
165
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
166
|
+
discoverDescriptors (callback) {
|
|
167
|
+
if (callback) {
|
|
168
|
+
this.once('descriptorsDiscover', (descriptors, error) => callback(error, descriptors));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
this._noble.discoverDescriptors(
|
|
172
|
+
this._peripheralId,
|
|
173
|
+
this._serviceUuid,
|
|
174
|
+
this.uuid
|
|
175
|
+
);
|
|
176
|
+
}
|
|
157
177
|
|
|
158
|
-
|
|
159
|
-
|
|
178
|
+
async discoverDescriptorsAsync () {
|
|
179
|
+
return this._noble._withDisconnectHandler(this._peripheralId, () => {
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
this.discoverDescriptors((error, descriptors) => error ? reject(error) : resolve(descriptors));
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
160
186
|
|
|
161
187
|
module.exports = Characteristic;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
3
|
#include <napi.h>
|
|
4
|
-
#include
|
|
4
|
+
#include <vector>
|
|
5
|
+
#include <string>
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
#include "Peripheral.h"
|
|
8
|
+
#include "ThreadSafeCallback.h"
|
|
7
9
|
|
|
8
10
|
class Emit
|
|
9
11
|
{
|
|
@@ -11,22 +13,23 @@ public:
|
|
|
11
13
|
// clang-format off
|
|
12
14
|
void Wrap(const Napi::Value& receiver, const Napi::Function& callback);
|
|
13
15
|
void RadioState(const std::string& status);
|
|
16
|
+
void Address(const std::string& address);
|
|
14
17
|
void ScanState(bool start);
|
|
15
18
|
void Scan(const std::string& uuid, int rssi, const Peripheral& peripheral);
|
|
16
19
|
void Connected(const std::string& uuid, const std::string& error = "");
|
|
17
20
|
void Disconnected(const std::string& uuid);
|
|
18
|
-
void RSSI(const std::string& uuid, int rssi);
|
|
19
|
-
void ServicesDiscovered(const std::string& uuid, const std::vector<std::string>& serviceUuids);
|
|
20
|
-
void IncludedServicesDiscovered(const std::string& uuid, const std::string& serviceUuid, const std::vector<std::string>& serviceUuids);
|
|
21
|
-
void CharacteristicsDiscovered(const std::string& uuid, const std::string& serviceUuid, const std::vector<std::pair<std::string, std::vector<std::string>>>& characteristics);
|
|
22
|
-
void Read(const std::string& uuid, const std::string& serviceUuid, const std::string& characteristicUuid, const Data& data, bool isNotification);
|
|
23
|
-
void Write(const std::string& uuid, const std::string& serviceUuid, const std::string& characteristicUuid);
|
|
24
|
-
void Notify(const std::string& uuid, const std::string& serviceUuid, const std::string& characteristicUuid, bool state);
|
|
25
|
-
void DescriptorsDiscovered(const std::string& uuid, const std::string& serviceUuid, const std::string& characteristicUuid, const std::vector<std::string>& descriptorUuids);
|
|
26
|
-
void ReadValue(const std::string& uuid, const std::string& serviceUuid, const std::string& characteristicUuid, const std::string& descriptorUuid, const Data& data);
|
|
27
|
-
void WriteValue(const std::string& uuid, const std::string& serviceUuid, const std::string& characteristicUuid, const std::string& descriptorUuid);
|
|
28
|
-
void ReadHandle(const std::string& uuid, int descriptorHandle, const Data& data);
|
|
29
|
-
void WriteHandle(const std::string& uuid, int descriptorHandle);
|
|
21
|
+
void RSSI(const std::string& uuid, int rssi, const std::string& error = "");
|
|
22
|
+
void ServicesDiscovered(const std::string& uuid, const std::vector<std::string>& serviceUuids, const std::string& error = "");
|
|
23
|
+
void IncludedServicesDiscovered(const std::string& uuid, const std::string& serviceUuid, const std::vector<std::string>& serviceUuids, const std::string& error = "");
|
|
24
|
+
void CharacteristicsDiscovered(const std::string& uuid, const std::string& serviceUuid, const std::vector<std::pair<std::string, std::vector<std::string>>>& characteristics, const std::string& error = "");
|
|
25
|
+
void Read(const std::string& uuid, const std::string& serviceUuid, const std::string& characteristicUuid, const Data& data, bool isNotification, const std::string& error = "");
|
|
26
|
+
void Write(const std::string& uuid, const std::string& serviceUuid, const std::string& characteristicUuid, const std::string& error = "");
|
|
27
|
+
void Notify(const std::string& uuid, const std::string& serviceUuid, const std::string& characteristicUuid, bool state, const std::string& error = "");
|
|
28
|
+
void DescriptorsDiscovered(const std::string& uuid, const std::string& serviceUuid, const std::string& characteristicUuid, const std::vector<std::string>& descriptorUuids, const std::string& error = "");
|
|
29
|
+
void ReadValue(const std::string& uuid, const std::string& serviceUuid, const std::string& characteristicUuid, const std::string& descriptorUuid, const Data& data, const std::string& error = "");
|
|
30
|
+
void WriteValue(const std::string& uuid, const std::string& serviceUuid, const std::string& characteristicUuid, const std::string& descriptorUuid, const std::string& error = "");
|
|
31
|
+
void ReadHandle(const std::string& uuid, int descriptorHandle, const Data& data, const std::string& error = "");
|
|
32
|
+
void WriteHandle(const std::string& uuid, int descriptorHandle, const std::string& error = "");
|
|
30
33
|
// clang-format on
|
|
31
34
|
protected:
|
|
32
35
|
std::shared_ptr<ThreadSafeCallback> mCallback;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <vector>
|
|
4
|
+
#include <string>
|
|
5
|
+
#include <optional>
|
|
6
|
+
|
|
7
|
+
using Data = std::vector<uint8_t>;
|
|
8
|
+
|
|
9
|
+
enum AddressType {
|
|
10
|
+
PUBLIC,
|
|
11
|
+
RANDOM,
|
|
12
|
+
UNKNOWN,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
class Peripheral {
|
|
16
|
+
public:
|
|
17
|
+
Peripheral():
|
|
18
|
+
address("unknown"),
|
|
19
|
+
addressType(UNKNOWN),
|
|
20
|
+
connectable(false) {}
|
|
21
|
+
|
|
22
|
+
std::string address;
|
|
23
|
+
AddressType addressType;
|
|
24
|
+
bool connectable;
|
|
25
|
+
|
|
26
|
+
std::optional<std::string> name;
|
|
27
|
+
std::optional<int> txPowerLevel;
|
|
28
|
+
std::optional<Data> manufacturerData;
|
|
29
|
+
std::optional<std::vector<std::pair<std::string, Data>>> serviceData;
|
|
30
|
+
std::optional<std::vector<std::string>> serviceUuids;
|
|
31
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <napi.h>
|
|
4
|
+
#include <vector>
|
|
5
|
+
#include <functional>
|
|
6
|
+
|
|
7
|
+
class ThreadSafeCallback {
|
|
8
|
+
public:
|
|
9
|
+
// More descriptive type aliases
|
|
10
|
+
using ArgumentVector = std::vector<napi_value>;
|
|
11
|
+
using ArgumentFunction = std::function<void(napi_env, ArgumentVector&)>;
|
|
12
|
+
|
|
13
|
+
// Constructor with validation
|
|
14
|
+
ThreadSafeCallback(const Napi::Value& receiver, const Napi::Function& jsCallback);
|
|
15
|
+
|
|
16
|
+
// Destructor
|
|
17
|
+
~ThreadSafeCallback();
|
|
18
|
+
|
|
19
|
+
// Delete copy and move operations explicitly
|
|
20
|
+
ThreadSafeCallback(const ThreadSafeCallback&) = delete;
|
|
21
|
+
ThreadSafeCallback& operator=(const ThreadSafeCallback&) = delete;
|
|
22
|
+
ThreadSafeCallback& operator=(ThreadSafeCallback&&) = delete;
|
|
23
|
+
|
|
24
|
+
// Public interface
|
|
25
|
+
void call(ArgumentFunction argFunction);
|
|
26
|
+
|
|
27
|
+
private:
|
|
28
|
+
// Static callback handler
|
|
29
|
+
static void callJsCallback(Napi::Env env,
|
|
30
|
+
Napi::Function jsCallback,
|
|
31
|
+
Napi::Reference<Napi::Value>* context,
|
|
32
|
+
ArgumentFunction* argFn);
|
|
33
|
+
|
|
34
|
+
// Type alias for the thread-safe function
|
|
35
|
+
using ThreadSafeFunc = Napi::TypedThreadSafeFunction<
|
|
36
|
+
Napi::Reference<Napi::Value>,
|
|
37
|
+
ArgumentFunction,
|
|
38
|
+
callJsCallback>;
|
|
39
|
+
|
|
40
|
+
// Member variables
|
|
41
|
+
Napi::Reference<Napi::Value> receiver_;
|
|
42
|
+
ThreadSafeFunc threadSafeFunction_;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Implementation
|
|
46
|
+
|
|
47
|
+
inline ThreadSafeCallback::ThreadSafeCallback(
|
|
48
|
+
const Napi::Value& receiver,
|
|
49
|
+
const Napi::Function& jsCallback) {
|
|
50
|
+
|
|
51
|
+
if (!(receiver.IsObject() || receiver.IsFunction())) {
|
|
52
|
+
throw Napi::Error::New(jsCallback.Env(),
|
|
53
|
+
"Callback receiver must be an object or function");
|
|
54
|
+
}
|
|
55
|
+
if (!jsCallback.IsFunction()) {
|
|
56
|
+
throw Napi::Error::New(jsCallback.Env(),
|
|
57
|
+
"Callback must be a function");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
receiver_ = Napi::Persistent(receiver);
|
|
61
|
+
threadSafeFunction_ = ThreadSafeFunc::New(
|
|
62
|
+
jsCallback.Env(),
|
|
63
|
+
jsCallback,
|
|
64
|
+
"ThreadSafeCallback callback",
|
|
65
|
+
0, 1,
|
|
66
|
+
&receiver_);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
inline ThreadSafeCallback::~ThreadSafeCallback() {
|
|
70
|
+
threadSafeFunction_.Abort();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
inline void ThreadSafeCallback::call(ArgumentFunction argFunction) {
|
|
74
|
+
auto argFn = new ArgumentFunction(argFunction);
|
|
75
|
+
if (threadSafeFunction_.BlockingCall(argFn) != napi_ok) {
|
|
76
|
+
delete argFn;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
inline void ThreadSafeCallback::callJsCallback(
|
|
81
|
+
Napi::Env env,
|
|
82
|
+
Napi::Function jsCallback,
|
|
83
|
+
Napi::Reference<Napi::Value>* context,
|
|
84
|
+
ArgumentFunction* argFn) {
|
|
85
|
+
|
|
86
|
+
if (argFn != nullptr) {
|
|
87
|
+
ArgumentVector args;
|
|
88
|
+
(*argFn)(env, args);
|
|
89
|
+
delete argFn;
|
|
90
|
+
|
|
91
|
+
if (env != nullptr && jsCallback != nullptr) {
|
|
92
|
+
jsCallback.Call(context->Value(), args);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|