@mikrojs/native 0.5.1 → 0.6.0-pr-70.gc88e298

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.
@@ -1,8 +1,10 @@
1
+ import {Observable} from 'mikrojs/observable'
1
2
  import {ok} from 'mikrojs/result'
2
3
  import {bind as nativeBind, type NativeUdpSocket} from 'native:udp'
3
4
 
5
+ import type {Subscriber} from '../observable/types.js'
4
6
  import type {Result} from '../result/types.js'
5
- import type {BindOptions, MulticastGroup, PeerAddress, UdpError, UdpSocket} from './types.js'
7
+ import type {BindOptions, MulticastGroup, UdpError, UdpMessage, UdpSocket} from './types.js'
6
8
 
7
9
  const utf8 = new TextEncoder()
8
10
 
@@ -11,7 +13,37 @@ function toBytes(data: Uint8Array | string): Uint8Array {
11
13
  }
12
14
 
13
15
  function makeSocket(handle: NativeUdpSocket): UdpSocket {
14
- let onMessage: ((msg: Uint8Array, from: PeerAddress) => void) | null = null
16
+ /* Lazy native attach: keep handle.setOnMessage in sync with whether any
17
+ * JS subscriber is listening. With zero subscribers, the native side
18
+ * skips JS dispatch and increments the dropped counter for inbound
19
+ * packets — preserving the pre-Observable behavior of unset onMessage
20
+ * silently discarding traffic. */
21
+ const subscribers: Subscriber<UdpMessage>[] = []
22
+ let closed = false
23
+
24
+ const onMessage = new Observable<UdpMessage>((sub) => {
25
+ if (closed) {
26
+ sub.complete()
27
+ return
28
+ }
29
+ subscribers.push(sub)
30
+ if (subscribers.length === 1) {
31
+ handle.setOnMessage((msg, from) => {
32
+ /* Snapshot so unsubscribes during dispatch don't shift indices. */
33
+ const snapshot = subscribers.slice()
34
+ for (const s of snapshot) {
35
+ if (!s.closed) s.next({msg, from})
36
+ }
37
+ })
38
+ }
39
+ sub.addTeardown(() => {
40
+ const i = subscribers.indexOf(sub)
41
+ if (i >= 0) subscribers.splice(i, 1)
42
+ if (subscribers.length === 0 && !closed) {
43
+ handle.setOnMessage(null)
44
+ }
45
+ })
46
+ })
15
47
 
16
48
  const sock: UdpSocket = {
17
49
  get port() {
@@ -26,13 +58,7 @@ function makeSocket(handle: NativeUdpSocket): UdpSocket {
26
58
  set dropped(value: number) {
27
59
  handle.dropped = value
28
60
  },
29
- get onMessage() {
30
- return onMessage
31
- },
32
- set onMessage(fn) {
33
- onMessage = fn
34
- handle.setOnMessage(fn)
35
- },
61
+ onMessage,
36
62
  send(data, to) {
37
63
  return handle.send(toBytes(data), to)
38
64
  },
@@ -43,6 +69,16 @@ function makeSocket(handle: NativeUdpSocket): UdpSocket {
43
69
  return handle.leaveMulticastGroup(group.address)
44
70
  },
45
71
  close() {
72
+ if (closed) return
73
+ closed = true
74
+ handle.setOnMessage(null)
75
+ /* Complete any active subscriptions. Walk a snapshot in case a
76
+ * complete handler unsubscribes a sibling. */
77
+ const snapshot = subscribers.slice()
78
+ subscribers.length = 0
79
+ for (const s of snapshot) {
80
+ if (!s.closed) s.complete()
81
+ }
46
82
  handle.close()
47
83
  },
48
84
  }
@@ -1,3 +1,4 @@
1
+ import type {Observable} from '../observable/types.js'
1
2
  import type {Result} from '../result/types.js'
2
3
 
3
4
  export type WifiStatus =
@@ -87,17 +88,6 @@ export interface ApStartOptions {
87
88
  maxConnections?: number
88
89
  }
89
90
 
90
- export interface WifiEventMap {
91
- connect: (info: WifiConnectionInfo) => void
92
- disconnect: (reason: WifiDisconnectReason) => void
93
- 'rssi-low': (rssi: number) => void
94
- }
95
-
96
- export interface ApEventMap {
97
- 'station-connect': (info: ApStationInfo) => void
98
- 'station-disconnect': (info: ApStationInfo) => void
99
- }
100
-
101
91
  export type WifiError =
102
92
  | {name: 'InitFailed'; message: string}
103
93
  | {name: 'CountryNotSet'}
@@ -122,8 +112,8 @@ export interface WifiAp {
122
112
  readonly stations: ApStationInfo[]
123
113
  deauthStation(mac: string): Result<void, WifiError>
124
114
  inactiveTimeout: number
125
- on<K extends keyof ApEventMap>(event: K, listener: ApEventMap[K]): void
126
- off<K extends keyof ApEventMap>(event: K, listener: ApEventMap[K]): void
115
+ readonly onStationConnect: Observable<ApStationInfo>
116
+ readonly onStationDisconnect: Observable<ApStationInfo>
127
117
  }
128
118
 
129
119
  export interface Wifi {
@@ -135,8 +125,9 @@ export interface Wifi {
135
125
  isConnected: boolean
136
126
  scan(opts?: ScanOptions): Promise<Result<ScanResult[], WifiError>>
137
127
 
138
- on<K extends keyof WifiEventMap>(event: K, listener: WifiEventMap[K]): void
139
- off<K extends keyof WifiEventMap>(event: K, listener: WifiEventMap[K]): void
128
+ readonly onConnect: Observable<WifiConnectionInfo>
129
+ readonly onDisconnect: Observable<WifiDisconnectReason>
130
+ readonly onRssiLow: Observable<number>
140
131
 
141
132
  readonly mac: string
142
133
  hostname: string | undefined
@@ -1,9 +1,9 @@
1
+ import {Observable} from 'mikrojs/observable'
1
2
  import {err, ok} from 'mikrojs/result'
2
3
  import {Wifi as NativeWifi} from 'native:wifi'
3
4
 
4
5
  import type {Result} from '../result/types.js'
5
6
  import type {
6
- ApEventMap,
7
7
  ApStartOptions,
8
8
  ApStationInfo,
9
9
  IpConfig,
@@ -15,8 +15,8 @@ import type {
15
15
  WifiAp,
16
16
  WifiConnectionInfo,
17
17
  WifiCountryCode,
18
+ WifiDisconnectReason,
18
19
  WifiError,
19
- WifiEventMap,
20
20
  WifiStatus,
21
21
  } from './types.js'
22
22
 
@@ -37,6 +37,23 @@ const StatusFromCode = new Map<number, WifiStatus>(
37
37
 
38
38
  const native = new NativeWifi()
39
39
 
40
+ /* One Observable per event type, each backed by a single native.on
41
+ * registration. Subscribers share that registration via the multicast
42
+ * source from Observable.withEmitters() — registering the listener once
43
+ * keeps the C-level callback list at minimum size regardless of how many
44
+ * JS subscribers are attached. */
45
+ const _onConnect = Observable.withEmitters<WifiConnectionInfo>()
46
+ const _onDisconnect = Observable.withEmitters<WifiDisconnectReason>()
47
+ const _onRssiLow = Observable.withEmitters<number>()
48
+ const _onStationConnect = Observable.withEmitters<ApStationInfo>()
49
+ const _onStationDisconnect = Observable.withEmitters<ApStationInfo>()
50
+
51
+ native.on('connect', (info) => _onConnect.next(info as WifiConnectionInfo))
52
+ native.on('disconnect', (reason) => _onDisconnect.next(reason as WifiDisconnectReason))
53
+ native.on('rssi-low', (rssi) => _onRssiLow.next(rssi as number))
54
+ native.on('station-connect', (info) => _onStationConnect.next(info as ApStationInfo))
55
+ native.on('station-disconnect', (info) => _onStationDisconnect.next(info as ApStationInfo))
56
+
40
57
  const ap: WifiAp = {
41
58
  start(options: ApStartOptions): Result<void, WifiError> {
42
59
  return native.apStart(options as Parameters<typeof native.apStart>[0])
@@ -71,13 +88,8 @@ const ap: WifiAp = {
71
88
  native.apSetInactiveTimeout(seconds)
72
89
  },
73
90
 
74
- on<K extends keyof ApEventMap>(event: K, listener: ApEventMap[K]) {
75
- native.on(event, listener as (...args: unknown[]) => void)
76
- },
77
-
78
- off<K extends keyof ApEventMap>(event: K, listener: ApEventMap[K]) {
79
- native.off(event, listener as (...args: unknown[]) => void)
80
- },
91
+ onStationConnect: _onStationConnect.observable,
92
+ onStationDisconnect: _onStationDisconnect.observable,
81
93
  }
82
94
 
83
95
  const MAX_CONNECT_RETRIES = 5
@@ -135,13 +147,9 @@ const wifi: Wifi = {
135
147
  return ok(asyncResult.value as ScanResult[])
136
148
  },
137
149
 
138
- on<K extends keyof WifiEventMap>(event: K, listener: WifiEventMap[K]) {
139
- native.on(event, listener as (...args: unknown[]) => void)
140
- },
141
-
142
- off<K extends keyof WifiEventMap>(event: K, listener: WifiEventMap[K]) {
143
- native.off(event, listener as (...args: unknown[]) => void)
144
- },
150
+ onConnect: _onConnect.observable,
151
+ onDisconnect: _onDisconnect.observable,
152
+ onRssiLow: _onRssiLow.observable,
145
153
 
146
154
  get mac(): string {
147
155
  const result = native.mac()