Skip to content

Commit 3ec61e5

Browse files
authored
fix: handle variable arguments in socket.off (#24)
* fix(android): handle variable arguments in socket.off * chore(package): remove unused dependencies * style(eslint): lint js files * fix: use HashSet instead of ArrayList * chore: remove debug code * chore: remove unused file * ci: test with 8.3.1.GA * test(unit): remove duplicate test * fix(ios): properly remove specific event handler * chore: remove debug output
1 parent 4825318 commit 3ec61e5

16 files changed

Lines changed: 2536 additions & 2277 deletions

.eslintrc.yml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1+
root: true
12
extends:
23
- axway/env-node
3-
- axway/titanium
4-
plugins:
5-
- jasmine
6-
env:
7-
jasmine: true
4+
- axway/env-titanium

Jenkinsfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
library 'pipeline-library'
33

44
buildModule {
5-
sdkVersion = '8.0.0.GA'
5+
sdkVersion = '8.3.1.GA'
66
}

android/assets/ti.socketio.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/**
2+
* CommonJS module to override the default event handling of the native SocketProxy
3+
*
4+
* Kroll proxies have the EventEmitter prototype built-in automcatically, which is not
5+
* compatible with the socket.io API.
6+
*/
7+
8+
var prototypePatched = false;
9+
10+
function connect() {
11+
var socket = this._nativeConnect.apply(this, arguments);
12+
if (!prototypePatched) {
13+
patchSocketPrototype(socket);
14+
prototypePatched = true;
15+
}
16+
return socket;
17+
}
18+
19+
function Manager() {
20+
var manager = this._nativeManager.apply(this, arguments);
21+
if (prototypePatched) {
22+
return manager;
23+
}
24+
25+
var originalSocketFunc = manager.socket;
26+
manager.socket = function socket() {
27+
var socket = originalSocketFunc.apply(manager, arguments);
28+
if (!prototypePatched) {
29+
patchSocketPrototype(socket);
30+
prototypePatched = true;
31+
}
32+
return socket;
33+
};
34+
return manager;
35+
}
36+
37+
function patchSocketPrototype(socket) {
38+
var prototype = Object.getPrototypeOf(socket);
39+
// We need to use Object.defineProperties here because assignment is blocked
40+
// by properties already defined on Android's EventEmitter prototype.
41+
Object.defineProperties(prototype, {
42+
on: {
43+
value: function on(eventName, listener) {
44+
if (typeof eventName !== 'string') {
45+
throw new TypeError('The event name must be of type "string". Received "' + typeof eventName + '"');
46+
}
47+
if (typeof listener !== 'function') {
48+
throw new TypeError('The event listener must be of type "function". Received "' + typeof listener + '"');
49+
}
50+
51+
if (!this.__eventListeners) {
52+
this.__eventListeners = new Map();
53+
}
54+
55+
var eventListeners = this.__eventListeners;
56+
var listenerId = this._nativeOn(eventName, listener);
57+
var listeners = eventListeners.get(eventName);
58+
if (!listeners) {
59+
listeners = new Map();
60+
eventListeners.set(eventName, listeners);
61+
}
62+
listeners.set(listener, listenerId);
63+
return this;
64+
},
65+
configurable: true,
66+
writable: true,
67+
enumerable: true
68+
},
69+
once: {
70+
value: function once(eventName, listener) {
71+
this.on(eventName, function () {
72+
this.off(eventName, listener);
73+
listener.apply(null, arguments);
74+
});
75+
return this;
76+
},
77+
configurable: true,
78+
writable: true,
79+
enumerable: true
80+
},
81+
off: {
82+
value: function off(eventName, listener) {
83+
var eventListeners = this.__eventListeners || (this.__eventListeners = new Map());
84+
var listeners;
85+
if (!eventName && !listener) {
86+
this._nativeOff();
87+
eventListeners.clear();
88+
} else if (eventName && !listener) {
89+
this._nativeOff(eventName);
90+
listeners = eventListeners.get(eventName);
91+
if (listeners) {
92+
listeners.delete(listener);
93+
}
94+
} else if (eventName && listener) {
95+
listeners = eventListeners.get(eventName);
96+
if (listeners) {
97+
var listenerId = listeners.get(listener);
98+
if (listenerId !== undefined) {
99+
this._nativeOff(eventName, listenerId);
100+
listeners.delete(listener);
101+
}
102+
}
103+
} else {
104+
throw new TypeError('Invalid arguments received.');
105+
}
106+
107+
return this;
108+
},
109+
configurable: true,
110+
writable: true,
111+
enumerable: true
112+
}
113+
});
114+
}
115+
116+
module.exports = {
117+
connect: connect,
118+
Manager: Manager
119+
};

android/manifest

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ moduleid: ti.socketio
1616
guid: 65fbdc59-432f-40d8-8ea9-02799887db3c
1717
platform: android
1818
minsdk: 7.0.0.GA
19+
commonjs: true

android/src/com/appc/titanium/socketio/SocketClientProxy.java

Lines changed: 38 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,14 @@
1919
import org.json.JSONException;
2020
import org.json.JSONObject;
2121

22-
import android.util.Pair;
23-
2422
import java.util.ArrayList;
2523
import java.util.HashMap;
2624
import java.util.HashSet;
2725
import java.util.Iterator;
2826

2927
import io.socket.client.Ack;
3028
import io.socket.client.Socket;
29+
import io.socket.client.Manager;
3130
import io.socket.emitter.Emitter.Listener;
3231

3332
@Kroll.proxy(creatableInModule=TiSocketioModule.class)
@@ -39,7 +38,9 @@ public class SocketClientProxy extends KrollProxy
3938

4039
private Socket socket;
4140
private SocketManagerProxy manager;
42-
private HashMap<String, HashSet<Pair<KrollFunction, Listener>>> eventHandlers;
41+
private int listenerId = 0;
42+
private HashMap<Integer, Listener> listeners;
43+
private HashMap<String, HashSet<Integer>> eventListenerIds;
4344

4445
// Constructor
4546
public SocketClientProxy(Socket socket, SocketManagerProxy manager)
@@ -48,7 +49,9 @@ public SocketClientProxy(Socket socket, SocketManagerProxy manager)
4849

4950
this.socket = socket;
5051
this.manager = manager;
51-
this.eventHandlers = new HashMap<String, HashSet<Pair<KrollFunction, Listener>>>();
52+
53+
this.listeners = new HashMap<Integer, Listener>();
54+
this.eventListenerIds = new HashMap<String, HashSet<Integer>>();
5255
}
5356

5457
// Properties
@@ -84,8 +87,7 @@ public void connect()
8487
}
8588

8689
@Kroll.method
87-
public SocketClientProxy on(final String eventName, final KrollFunction callback)
88-
{
90+
public int _nativeOn(final String eventName, final KrollFunction callback) {
8991
Listener listener = new Listener() {
9092
@Override
9193
public void call(Object... args)
@@ -94,52 +96,42 @@ public void call(Object... args)
9496
}
9597
};
9698
this.socket.on(eventName, listener);
97-
this.storeEventHandler(eventName, callback, listener);
98-
99-
return this;
99+
this.listeners.put(++this.listenerId, listener);
100+
HashSet<Integer> listenerIds = this.eventListenerIds.get(eventName);
101+
if (listenerIds == null) {
102+
listenerIds = new HashSet<Integer>();
103+
this.eventListenerIds.put(eventName, listenerIds);
104+
}
105+
listenerIds.add(this.listenerId);
106+
return this.listenerId;
100107
}
101108

102109
@Kroll.method
103-
public SocketClientProxy once(String eventName, final KrollFunction callback)
110+
public SocketClientProxy _nativeOff(Object[] args)
104111
{
105-
Listener listener = new Listener() {
106-
@Override
107-
public void call(Object... args)
108-
{
109-
callback.call(getKrollObject(), convertArguments(args));
112+
if (args.length == 0) {
113+
this.socket.off();
114+
this.listeners.clear();
115+
} else if (args.length == 1) {
116+
String eventName = TiConvert.toString(args[0]);
117+
this.socket.off(eventName);
118+
HashSet<Integer> listenerIds = this.eventListenerIds.get(eventName);
119+
if (listenerIds != null) {
120+
for (Integer listenerId : listenerIds) {
121+
this.listeners.remove(listenerId);
122+
}
123+
this.eventListenerIds.remove(eventName);
110124
}
111-
};
112-
this.socket.once(eventName, listener);
113-
this.storeEventHandler(eventName, callback, listener);
114-
115-
return this;
116-
}
117-
118-
@Kroll.method
119-
public SocketClientProxy off()
120-
{
121-
this.socket.off();
122-
this.removeAllEventHandlers();
123-
124-
return this;
125-
}
126-
127-
@Kroll.method
128-
public SocketClientProxy off(String eventName)
129-
{
130-
this.socket.off(eventName);
131-
this.removeEventHandler(eventName);
132-
133-
return this;
134-
}
135-
136-
@Kroll.method
137-
public SocketClientProxy off(String eventName, KrollFunction callback)
138-
{
139-
Listener listener = this.findListener(eventName, callback);
140-
if (listener != null) {
125+
} else if (args.length == 2) {
126+
String eventName = TiConvert.toString(args[0]);
127+
int listenerId = TiConvert.toInt(args[1]);
128+
Listener listener = this.listeners.get(listenerId);
141129
this.socket.off(eventName, listener);
142-
this.removeEventHandler(eventName, callback);
130+
this.listeners.remove(listenerId);
131+
HashSet<Integer> listenerIds = this.eventListenerIds.get(eventName);
132+
if (listenerIds != null) {
133+
listenerIds.remove(listenerId);
134+
}
143135
}
144136

145137
return this;
@@ -226,48 +218,4 @@ private Object convertAndSanitizePayload(Object value) {
226218

227219
return value;
228220
}
229-
230-
private void storeEventHandler(String eventName, KrollFunction handler, Listener listener) {
231-
HashSet<Pair<KrollFunction, Listener>> handlers = this.eventHandlers.get(eventName);
232-
if (handlers == null) {
233-
handlers = new HashSet<Pair<KrollFunction, Listener>>();
234-
}
235-
handlers.add(new Pair<KrollFunction, Listener>(handler, listener));
236-
this.eventHandlers.put(eventName, handlers);
237-
}
238-
239-
private void removeEventHandler(String eventName) {
240-
this.eventHandlers.remove(eventName);
241-
}
242-
243-
private void removeEventHandler(String eventName, KrollFunction handler) {
244-
HashSet<Pair<KrollFunction, Listener>> handlers = this.eventHandlers.get(eventName);
245-
if (handlers == null) {
246-
handlers = new HashSet<Pair<KrollFunction, Listener>>();
247-
}
248-
for (Iterator<Pair<KrollFunction, Listener>> i = handlers.iterator(); i.hasNext();) {
249-
Pair<KrollFunction, Listener> element = i.next();
250-
if (element.first == handler) {
251-
i.remove();
252-
}
253-
}
254-
}
255-
256-
private void removeAllEventHandlers() {
257-
this.eventHandlers.clear();
258-
}
259-
260-
private Listener findListener(String eventName, KrollFunction callback) {
261-
HashSet<Pair<KrollFunction, Listener>> handlers = this.eventHandlers.get(eventName);
262-
if (handlers == null) {
263-
handlers = new HashSet<Pair<KrollFunction, Listener>>();
264-
}
265-
for (Pair<KrollFunction, Listener> handlerPair : handlers) {
266-
if (handlerPair.first == callback) {
267-
return handlerPair.second;
268-
}
269-
}
270-
271-
return null;
272-
}
273221
}

android/src/com/appc/titanium/socketio/TiSocketioModule.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99
package com.appc.titanium.socketio;
1010

11+
import org.appcelerator.kroll.KrollFunction;
1112
import org.appcelerator.kroll.KrollModule;
1213
import org.appcelerator.kroll.annotations.Kroll;
1314
import org.appcelerator.kroll.common.Log;
@@ -51,14 +52,11 @@ public TiSocketioModule()
5152
// JS Methods
5253

5354
@Kroll.method
54-
public SocketClientProxy connect(String uri, @Kroll.argument(optional=true) HashMap jsOptions) throws URISyntaxException
55+
public SocketClientProxy _nativeConnect(String uri, @Kroll.argument(optional=true) HashMap jsOptions) throws URISyntaxException
5556
{
5657
boolean autoConnect = jsOptions != null ? TiConvert.toBoolean(jsOptions, "autoConnect", true) : true;
5758
Options options = this.convertOptions(jsOptions);
5859
Socket socket = IO.socket(uri, options);
59-
if (autoConnect) {
60-
socket.connect();
61-
}
6260
SocketManagerProxy managerProxy;
6361
if (this.managerCache.containsKey(socket.io())) {
6462
managerProxy = this.managerCache.get(socket.io());
@@ -67,12 +65,15 @@ public SocketClientProxy connect(String uri, @Kroll.argument(optional=true) Hash
6765
this.managerCache.put(socket.io(), managerProxy);
6866
}
6967
SocketClientProxy socketProxy = new SocketClientProxy(socket, managerProxy);
68+
if (autoConnect) {
69+
socket.connect();
70+
}
7071

7172
return socketProxy;
7273
}
7374

7475
@Kroll.method
75-
public SocketManagerProxy Manager(String uri, @Kroll.argument(optional=true) HashMap jsOptions) throws URISyntaxException
76+
public SocketManagerProxy _nativeManager(String uri, @Kroll.argument(optional=true) HashMap jsOptions) throws URISyntaxException
7677
{
7778
Options options = this.convertOptions(jsOptions);
7879
Manager manager = new Manager(new URI(uri), options);

0 commit comments

Comments
 (0)