1 module sockjsclient.client;
2 
3 import std.stdio;
4 import vibe.d;
5 import std.uuid;
6 import std.random;
7 import std.regex;
8 import std.string;
9 
10 class SockJsClient
11 {	
12 private:
13 	string m_url_send;
14 	string m_url_poll;
15 	enum ConnectionState { none, connecting, connected, clientDisconnected, hostDisconnected }
16 	ConnectionState m_connState = ConnectionState.none;
17 public:
18 	
19 	alias void delegate() EventOnConnect;
20 	alias void delegate(string) EventOnData;
21 	alias void delegate(int,string) EventOnDisconnect;
22 
23 	EventOnConnect		OnConnect;
24 	EventOnData			OnData;	
25 	EventOnDisconnect	OnDisconnect;
26 
27 	@property bool isConnected() const { return m_connState == ConnectionState.connected; }
28 	@property bool isConnecting() const { return m_connState == ConnectionState.connecting; }
29 
30 	///
31 	this(string host, string prefix)
32 	{
33 		auto randomUUId = randomUUID();
34 		auto randomInt = uniform(100,999);
35 		auto url = format("%s/%s/%s/%s/xhr",host,prefix,randomInt,randomUUId);
36 		m_url_poll = url;
37 		m_url_send = format("%s_send",m_url_poll);
38 	}
39 
40 	///
41 	public void connect() 
42 	{
43 		if (m_connState == ConnectionState.none) 
44 		{
45 			m_connState = ConnectionState.connecting;
46 			StartPoll();
47 		}
48 		else
49 		{
50 			throw new Exception("only one connect allowed");		
51 		}
52 	}
53 
54 	///
55 	public void disconnect()
56 	{
57 		//TODO: Should send an message on close url
58 		if (m_connState == ConnectionState.connected)
59 		{
60 			m_connState = ConnectionState.clientDisconnected;
61 		}
62 		else 
63 		{
64 			throw new Exception("Not connected");
65 		}
66 	}
67 
68 	///
69 	public void send(string message) 
70 	{	
71 		if (m_connState == ConnectionState.connected)
72 		{
73 			runTask({
74 				requestHTTP(m_url_send,
75 				            (scope HTTPClientRequest req) {
76 					req.method = HTTPMethod.POST;
77 					auto prep_message = "[\""~message~"\"]";
78 					req.writeBody(cast (ubyte[])prep_message,"text/plain");
79 					},
80 					(scope res) { 
81 						onSendResult(res);
82 					}
83 				);
84 			});
85 		} 
86 		else 
87 		{
88 			throw new Exception("Not connected");
89 		}
90 	}
91 
92 	///
93 	private void onSendResult( HTTPClientResponse res) {
94 		// TODO: Error handling - HTTP ERROR CODES
95 		//logInfo("Response: %s", res.bodyReader.readAllUTF8());
96 		if (res.statusCode != 204)
97 		{
98 			throw new Exception("Send error - Status Code: " ~ to!string(res.statusCode));
99 		}
100 
101 	}
102 
103 	///
104 	private void StartPoll()
105 	{
106 		if (m_connState == ConnectionState.connected || m_connState == ConnectionState.connecting)
107 		{
108 			requestHTTP(m_url_poll,
109 						(scope req) {},
110 						(scope res) { 
111 							onPollResult(res);
112 						}
113 			);
114 		}
115 		else
116 		{
117 			throw new Exception("Not connected");
118 		}
119 	}
120 
121 	///
122 	private void onPollResult( HTTPClientResponse res) 
123 	{
124 		auto content = res.bodyReader.readAllUTF8();
125 
126 		if (m_connState == ConnectionState.clientDisconnected)
127 			return;
128 		
129 		if (content == "o\n") 
130 		{
131 			m_connState = ConnectionState.connected;
132 
133 			CallOnConnect();
134 		} 
135 		else if (content == "h\n") 
136 		{
137 			//logInfo("got heartbeat");
138 		} 
139 		else 
140 		{
141 			if (m_connState == ConnectionState.connected) 
142 			{
143 				if (content[0] == 'a')
144 				{
145 					if (OnData != null)
146 					{
147 						auto arr = content[3..$-3];
148 						foreach(a; splitter(arr,regex(q"{","}")))
149 						{
150 							CallOnData(a);
151 						}
152 					}
153 				}
154 				else if (content[0] == 'c') 
155 				{
156 					m_connState = ConnectionState.hostDisconnected;
157 					if (OnDisconnect != null)
158 					{
159 						auto closeString = content[2..$-2];
160 						auto closeArray = closeString.split(",");
161 						if (closeArray.length >= 2) 
162 						{
163 							int closeCode = closeArray[0].to!int;
164 							string closeMessage = closeArray[1][1..$-1];
165 
166 							CallOnDisconnect(closeCode, closeMessage);
167 						}
168 						else 
169 						{
170 							CallOnDisconnect();
171 						}
172 					}
173 				}	
174 			}
175 		}
176 
177 		if (m_connState == ConnectionState.connected || m_connState == ConnectionState.connecting)
178 		{
179 			runTask({
180 				StartPoll();
181 			});
182 		}
183 	}
184 
185 	///
186 	private void CallOnConnect()
187 	{
188 		if(OnConnect)
189 		{
190 			runTask({
191 				OnConnect();
192 			});
193 		}
194 	}
195 
196 	///
197 	private void CallOnData(string _msg)
198 	{
199 		if(OnData)
200 		{
201 			runTask({
202 				OnData(_msg);
203 			});
204 		}
205 	}
206 
207 	///
208 	private void CallOnDisconnect(int _code=0, string _msg="")
209 	{
210 		if(OnDisconnect)
211 		{
212 			runTask({
213 				if(OnDisconnect)
214 					OnDisconnect(_code, _msg);
215 			});
216 		}
217 	}
218 }
219