@mikrojs/native 0.6.0-pr-72.g464c29c → 0.6.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.
@@ -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 UdpFamily = 'ipv4' | 'ipv6'
@@ -31,19 +32,31 @@ export type UdpError =
31
32
  | {name: 'Closed'}
32
33
  | {name: 'NotBound'}
33
34
 
35
+ /** Inbound datagram delivered via `socket.onMessage`. */
36
+ export interface UdpMessage {
37
+ msg: Uint8Array
38
+ from: PeerAddress
39
+ }
40
+
34
41
  export interface UdpSocket {
35
42
  readonly port: number
36
43
  readonly family: UdpFamily | 'dual'
37
44
  /**
38
45
  * Counter incremented each time an inbound datagram is dropped:
39
46
  * - the per-socket queue is full,
40
- * - no `onMessage` handler is set when a packet arrives,
47
+ * - no subscriber is attached to `onMessage` when a packet arrives,
41
48
  * - or the datagram is larger than the 1500-byte receive buffer
42
49
  * (typical Ethernet MTU; covers CoAP / mDNS / SNTP / DNS).
43
50
  */
44
51
  dropped: number
45
52
 
46
- onMessage: ((msg: Uint8Array, from: PeerAddress) => void) | null
53
+ /**
54
+ * Stream of inbound datagrams. Subscribing attaches the native dispatch
55
+ * once the first subscriber is added; the last `unsubscribe()` detaches
56
+ * it again. Multiple subscribers fan out from one native registration.
57
+ * Completes when `close()` is called.
58
+ */
59
+ readonly onMessage: Observable<UdpMessage>
47
60
 
48
61
  send(data: Uint8Array | string, to: PeerAddress): Promise<Result<void, UdpError>>
49
62
  joinMulticastGroup(group: MulticastGroup): Result<void, UdpError>
@@ -1,8 +1,10 @@
1
1
  import {ok} from 'mikrojs/result'
2
+ import {Observable} from 'native:observable'
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,11 +125,12 @@ 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
- hostname: string | undefined
133
+ readonly hostname: string | undefined
143
134
  ipConfig(): Result<IpConfig | undefined, WifiError>
144
135
  ipConfig(opts: StaticIpConfig): Result<void, WifiError>
145
136
  ipConfig(opts?: StaticIpConfig): Result<IpConfig | undefined, WifiError> | Result<void, WifiError>
@@ -150,7 +141,7 @@ export interface Wifi {
150
141
  rssiThreshold: number
151
142
 
152
143
  powerSave: PowerSaveMode
153
- country: WifiCountryCode | undefined
144
+ readonly country: WifiCountryCode | undefined
154
145
  }
155
146
 
156
147
  export declare const wifi: Wifi
@@ -1,9 +1,9 @@
1
+ import {lazyEvent} from 'mikrojs/observable/lazy'
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
 
@@ -71,13 +71,8 @@ const ap: WifiAp = {
71
71
  native.apSetInactiveTimeout(seconds)
72
72
  },
73
73
 
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
- },
74
+ onStationConnect: lazyEvent<ApStationInfo>(native, 'station-connect'),
75
+ onStationDisconnect: lazyEvent<ApStationInfo>(native, 'station-disconnect'),
81
76
  }
82
77
 
83
78
  const MAX_CONNECT_RETRIES = 5
@@ -135,13 +130,9 @@ const wifi: Wifi = {
135
130
  return ok(asyncResult.value as ScanResult[])
136
131
  },
137
132
 
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
- },
133
+ onConnect: lazyEvent<WifiConnectionInfo>(native, 'connect'),
134
+ onDisconnect: lazyEvent<WifiDisconnectReason>(native, 'disconnect'),
135
+ onRssiLow: lazyEvent<number>(native, 'rssi-low'),
145
136
 
146
137
  get mac(): string {
147
138
  const result = native.mac()
@@ -152,12 +143,6 @@ const wifi: Wifi = {
152
143
  return native.getHostname()
153
144
  },
154
145
 
155
- set hostname(value: string | undefined) {
156
- if (value !== undefined) {
157
- native.setHostname(value)
158
- }
159
- },
160
-
161
146
  ipConfig: ((
162
147
  opts?: StaticIpConfig,
163
148
  ): Result<IpConfig | undefined, WifiError> | Result<void, WifiError> => {
@@ -197,12 +182,6 @@ const wifi: Wifi = {
197
182
  get country(): WifiCountryCode | undefined {
198
183
  return native.getCountry() as WifiCountryCode | undefined
199
184
  },
200
-
201
- set country(cc: WifiCountryCode | undefined) {
202
- if (cc !== undefined) {
203
- native.setCountry(cc)
204
- }
205
- },
206
185
  }
207
186
 
208
187
  export {wifi}
@@ -19,6 +19,7 @@ void MIK_DefaultConfig(MIKConfig* config) {
19
19
  config->fs_read_max = 0; /* 0 = runtime default (65536) */
20
20
  config->entry_point[0] = '\0';
21
21
  config->wifi_country[0] = '\0';
22
+ config->wifi_hostname[0] = '\0';
22
23
  }
23
24
 
24
25
  /* Minimal JSON parser for config file — avoids cJSON dependency.
@@ -343,8 +344,10 @@ int MIK_LoadConfig(const char* base_path, MIKConfig* config) {
343
344
  if (mik__json_get_number(buf, "fsReadMax", &num_val)) {
344
345
  config->fs_read_max = (uint32_t)num_val;
345
346
  }
346
- mik__json_get_string(buf, "wifiCountry", config->wifi_country,
347
+ mik__json_get_string(buf, "wifi.country", config->wifi_country,
347
348
  sizeof(config->wifi_country));
349
+ mik__json_get_string(buf, "wifi.hostname", config->wifi_hostname,
350
+ sizeof(config->wifi_hostname));
348
351
 
349
352
  platform->log(MIK_LOG_INFO, TAG,
350
353
  "Loaded config: restart=%d delay=%dms stack=%u reserved=%lu",