Skip to content

Commit c6a0f3f

Browse files
feat: automatic dispose of probe on unsubscribe (#40)
BREAKING CHANGE: removes terminateProbe function and closes subscription via rxjs unsubscribe function. closes #39
1 parent 4fffeac commit c6a0f3f

File tree

8 files changed

+33
-37
lines changed

8 files changed

+33
-37
lines changed

README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,17 @@ $ camera-probe
5656

5757
## Programmatic Usage
5858
```js
59-
import { onvifDevices$, terminateProbe } from 'camera-probe'
59+
import { onvifDevices$ } from 'camera-probe'
60+
import { takeUntil } from 'rxjs/operators'
6061

61-
onvifDevices$().subscribe(console.log)
62+
const subscription = onvifDevices$().subscribe(console.log)
6263

6364
// be sure to close the socket connection when complete with your query
64-
// This is a tad awkward until a better solution to stopping the inner observables is achieved.
65-
terminateProbe()
65+
// by unsubscribing from the observable.
66+
subscription.unsubscribe()
67+
68+
// or using an rxjs operator like take
69+
onvifDevices$().pipe(takeUntil(someObservaleFires)).subscribe(console.log)
6670

6771
// results
6872
[ { name: 'Amcrest',

src/core/probe.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createSocket, RemoteInfo } from 'dgram'
22
import { Strings, Numbers, IProbeConfig, DEFAULT_PROBE_CONFIG } from './interfaces'
3-
import { Observable, Observer, fromEvent, timer } from 'rxjs'
3+
import { Observable, Observer, fromEvent, timer, Subject } from 'rxjs'
44
import { shareReplay, map, distinctUntilChanged, mapTo, takeUntil, scan } from 'rxjs/operators'
55

66
type IMessage = readonly [Buffer, RemoteInfo]
@@ -41,19 +41,19 @@ export const flattenBuffersWithInfo =
4141

4242
export const probe =
4343
(config?: Partial<IProbeConfig>) =>
44-
(messages: Strings) =>
45-
(until: Observable<any>): Observable<Strings> =>
44+
(messages: Strings): Observable<Strings> =>
4645
Observable.create((obs: Observer<Strings>) => {
4746
const cfg = { ...DEFAULT_PROBE_CONFIG, ...(config || {}) }
4847
const socket = createSocket({ type: 'udp4' })
4948
const socketMessages$ = fromEvent<IMessage>(socket, 'message').pipe(map(a => a[0]), shareReplay(1))
49+
const internalLimit = new Subject()
5050

5151
socket.on('err', err => obs.error(err))
5252
socket.on('close', () => obs.complete())
5353

5454
timer(0, cfg.PROBE_REQUEST_SAMPLE_RATE_MS).pipe(
5555
mapTo(flattenBuffersWithInfo(cfg.PORTS)(cfg.MULTICAST_ADDRESS)(messages.map(mapStringToBuffer))),
56-
takeUntil(until))
56+
takeUntil(internalLimit))
5757
.subscribe(bfrPorts => {
5858
bfrPorts.forEach(mdl => socket.send(mdl.buffer, 0, mdl.buffer.length, mdl.port, mdl.address))
5959
})
@@ -65,10 +65,12 @@ export const probe =
6565
distinctUntilObjectChanged,
6666
toArrayOfValues,
6767
flattenDocumentStrings,
68-
takeUntil(until)
69-
).subscribe(msg => obs.next(msg), undefined, () => {
70-
setTimeout(() => {
71-
socket.close()
72-
}, 1000)
73-
})
68+
takeUntil(internalLimit)
69+
).subscribe(msg => obs.next(msg))
70+
71+
return function unsubscribe() {
72+
internalLimit.next()
73+
internalLimit.complete()
74+
socket.close()
75+
}
7476
})

src/index.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11

2-
import { map, shareReplay } from 'rxjs/operators'
2+
import { map, share } from 'rxjs/operators'
33
import { onvifProbe } from './onvif/onvif-probe'
4-
import { Subject } from 'rxjs'
54

65
export * from './onvif/device'
76

8-
const end = new Subject()
9-
const end$ = end.asObservable()
10-
11-
export const terminateProbe = () => end.next()
12-
export const onvifProbe$ = () => onvifProbe()(end$).pipe(shareReplay(1))
7+
export const onvifProbe$ = () => onvifProbe().pipe(share())
138
export const onvifDevices$ = () => onvifProbe$().pipe(map(a => a.map(b => b.device)))
149
export const onvifResponses$ = () => onvifProbe$().pipe(map(a => a.map(b => b.raw)))
1510

src/onvif/onvif-probe.spec.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createSocket } from 'dgram'
22
import { onvifProbe } from './onvif-probe'
3-
import { Subject } from 'rxjs'
3+
import { take } from 'rxjs/operators'
44

55
const ipcam = '<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsdd="http://schemas.xmlsoap.org/ws/2005/04/discovery" xmlns:vfdis="http://www.onvif.org/ver10/network/wsdl/RemoteDiscoveryBinding" xmlns:vfdis2="http://www.onvif.org/ver10/network/wsdl/DiscoveryLookupBinding" xmlns:tdn="http://www.onvif.org/ver10/network/wsdl"><SOAP-ENV:Header><wsa:MessageID>uuid:8eceb0ca-564e-4436-bec5-e63ea243c529</wsa:MessageID><wsa:RelatesTo>uuid:NetworkVideoTransmitter</wsa:RelatesTo><wsa:ReplyTo SOAP-ENV:mustUnderstand="true"><wsa:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address></wsa:ReplyTo><wsa:To SOAP-ENV:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To><wsa:Action SOAP-ENV:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches</wsa:Action></SOAP-ENV:Header><SOAP-ENV:Body><wsdd:ProbeMatches><wsdd:ProbeMatch><wsa:EndpointReference><wsa:Address>urn:uuid:8eceb0ca-564e-4436-bec5-e63ea243c529</wsa:Address><wsa:ReferenceProperties></wsa:ReferenceProperties><wsa:ReferenceParameters></wsa:ReferenceParameters><wsa:PortType>ttl</wsa:PortType></wsa:EndpointReference><wsdd:Types>tdn:4655721b-4e0e-4296-ba0b-3180423b5b0c</wsdd:Types><wsdd:Scopes>onvif://www.onvif.org/Profile/Streaming onvif://www.onvif.org/Model/631GA onvif://www.onvif.org/Name/IPCAM onvif://www.onvif.org/location/country/china</wsdd:Scopes><wsdd:XAddrs>http://192.168.1.1:80/onvif/device_service</wsdd:XAddrs><wsdd:MetadataVersion>1</wsdd:MetadataVersion></wsdd:ProbeMatch></wsdd:ProbeMatches></SOAP-ENV:Body></SOAP-ENV:Envelope>'
66
const amcrest = '<?xml version="1.0" encoding="utf-8" standalone="yes" ?><s:Envelope xmlns:sc="http://www.w3.org/2003/05/soap-encoding" xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:dn="http://www.onvif.org/ver10/network/wsdl" xmlns:tds="http://www.onvif.org/ver10/device/wsdl" xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing"><s:Header><a:MessageID>uuid:8eceb0ca-564e-4436-bec5-e63ea243c529</a:MessageID><a:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery</a:To><a:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches</a:Action><a:RelatesTo>uuid:Device</a:RelatesTo></s:Header><s:Body><d:ProbeMatches><d:ProbeMatch><a:EndpointReference><a:Address>uuid:8eceb0ca-564e-4436-bec5-e63ea243c529</a:Address></a:EndpointReference><d:Types>dn:NetworkVideoTransmitter tds:Device</d:Types><d:Scopes>onvif://www.onvif.org/location/country/china onvif://www.onvif.org/name/Amcrest onvif://www.onvif.org/hardware/IP2M-841B onvif://www.onvif.org/Profile/Streaming onvif://www.onvif.org/type/Network_Video_Transmitter onvif://www.onvif.org/extension/unique_identifier</d:Scopes><d:XAddrs>http://192.168.1.235/onvif/device_service</d:XAddrs><d:MetadataVersion>1</d:MetadataVersion></d:ProbeMatch></d:ProbeMatches></s:Body></s:Envelope>'
@@ -26,10 +26,10 @@ const config = (port: number) => {
2626
describe('onvif-probe', () => {
2727
it('should handle IPCAM - 631GA', done => {
2828
const port = 41241
29-
const end = new Subject()
3029

3130
initTestServer(port)(ipcam)
32-
onvifProbe(config(port))(end)
31+
onvifProbe(config(port))
32+
.pipe(take(1))
3333
.subscribe(res => {
3434
expect(res[0].device).toEqual({
3535
name: 'IPCAM',
@@ -48,17 +48,16 @@ describe('onvif-probe', () => {
4848
profiles: ['Streaming'],
4949
xaddrs: ['http://192.168.1.1:80/onvif/device_service']
5050
})
51-
end.next()
5251
done()
5352
})
5453
})
5554

5655
it('should handle AMCREST - IP2M-841B', done => {
5756
const port = 41242
58-
const end = new Subject()
5957

6058
initTestServer(port)(amcrest)
61-
onvifProbe(config(port))(end)
59+
onvifProbe(config(port))
60+
.pipe(take(1))
6261
.subscribe(res => {
6362
expect(res[0].device).toEqual({
6463
name: 'Amcrest',
@@ -78,7 +77,6 @@ describe('onvif-probe', () => {
7877
profiles: ['Streaming'],
7978
xaddrs: ['http://192.168.1.235/onvif/device_service']
8079
})
81-
end.next()
8280
done()
8381
})
8482
})

src/onvif/onvif-probe.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ export type IOnvifProbeResponseModels = readonly IOnvifProbeResponseModel[]
1212
export type IOnvifProbeResponse = Observable<IOnvifProbeResponseModels>
1313

1414
export const onvifProbe =
15-
(config?: Partial<IWsProbeConfig>) => (until: Observable<any>): IOnvifProbeResponse =>
16-
wsProbe(config)(until)
15+
(config?: Partial<IWsProbeConfig>): IOnvifProbeResponse =>
16+
wsProbe(config)
1717
.pipe(map(res => res.map(a => {
1818
return {
1919
...a,

src/upnp/upnp-probe.spec.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { createSocket } from 'dgram'
22
import { upnpProbe } from './upnp-probe'
3-
import { Subject } from 'rxjs'
43

54
const initTestServer = (port: number) => {
65
const server = createSocket('udp4')
@@ -13,9 +12,8 @@ const initTestServer = (port: number) => {
1312
describe.skip('upnp probe', () => {
1413
it.skip('ddddd', done => {
1514
const port = 1900
16-
const end = new Subject()
1715

18-
upnpProbe({ PORTS: [port] })(end)
16+
upnpProbe({ PORTS: [port] })
1917
.subscribe(res => {
2018
console.log(res)
2119
})

src/ws-discovery/ws-probe.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ describe('ws probe', () => {
2929
const end$ = end.asObservable()
3030

3131
initTestServer(port)
32-
wsProbe(config(port))(end$)
32+
wsProbe(config(port))
3333
.subscribe(res => {
3434
const res1 = res[0]
3535
expect(res.length).toEqual(1)

src/ws-discovery/ws-probe.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@ import { map } from 'rxjs/operators'
44
import { probe } from '../core/probe'
55
import { IWsProbeConfig, IWsResponse } from './ws-probe.interfaces'
66
import { DEFAULT_WS_PROBE_CONFIG } from './config'
7-
import { Observable } from 'rxjs'
87

98
const mapDeviceStrToPayload = (str: string) => generateWsDiscoveryProbePayload(str)(generateGuid())
109
const mapDevicesToPayloads = (devices: readonly string[]) => devices.map(mapDeviceStrToPayload)
1110

1211
export const wsProbe =
13-
(config?: Partial<IWsProbeConfig>) => (until: Observable<any>): IWsResponse => {
12+
(config?: Partial<IWsProbeConfig>): IWsResponse => {
1413
const cfg = { ...DEFAULT_WS_PROBE_CONFIG, ...config } as IWsProbeConfig
15-
return probe(cfg)(mapDevicesToPayloads(cfg.DEVICES))(until)
14+
return probe(cfg)(mapDevicesToPayloads(cfg.DEVICES))
1615
.pipe(map(b => {
1716
return b.map(raw => {
1817
return {

0 commit comments

Comments
 (0)