Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 350 lines (310 sloc) 10.463 kB
b4f6652 @Aqua-Ye updated README and description
authored
1 /* A simple, one-room, scalable real-time web chat, with file sharing
b30538d initial commit
David Rajchenbach-Teller authored
2
59f9691 @Aqua-Ye started to implement a file sharing feature
authored
3 Copyright © 2012 Frederic Ye
626aad4 @Aqua-Ye inversed copyright order
authored
4 Copyright © 2010-2011 MLstate
b30538d initial commit
David Rajchenbach-Teller authored
5
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU Affero General Public License as
8 published by the Free Software Foundation, either version 3 of the
9 License, or (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU Affero General Public License for more details.
15
16 You should have received a copy of the GNU Affero General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
c984702 @Aqua-Ye removed useless import
authored
20 import stdlib.system
56e4e7e @Aqua-Ye [enhance] a lot of improvements: focus on input, auto_scroll working,…
authored
21
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
22 /** Constants **/
c437c43 @mattgu74 a user list
mattgu74 authored
23
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
24 GITHUB_USER = "Aqua-Ye"
25 GITHUB_REPO = "OpaChat"
26 NB_LAST_MSGS = 10
27
28 /** Types **/
29
d34acb4 @Aqua-Ye various cosmetic improvements
authored
30 type user = { int id, string name }
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
31 type source = { system } or { user user }
a5bcd22 @Aqua-Ye [clean] just some clean up: rename css.css into style.css, text/type …
authored
32 type message = {
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
33 source source,
34 string text,
35 Date.date date,
a5bcd22 @Aqua-Ye [clean] just some clean up: rename css.css into style.css, text/type …
authored
36 }
f2a5736 @Aqua-Ye added media type and wip on file upload / sharing
authored
37 type media = {
38 source source,
39 string name,
40 string src,
41 string mimetype,
42 Date.date date,
43 }
113e829 @Aqua-Ye [enhance] better network usage, and client connection/disconnection +…
authored
44 type client_channel = channel(void)
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
45 type network_msg =
46 {message message}
47 or {(user, client_channel) connection}
48 or {user disconnection}
49 or {stats}
f2a5736 @Aqua-Ye added media type and wip on file upload / sharing
authored
50 or {media media}
ac588c1 @Aqua-Ye [improvement] re-added users list + improve XHR
authored
51
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
52 /** Database **/
53
54 database intmap(message) /history
55
56 exposed Network.network(network_msg) room = Network.cloud("room")
57 private reference(list(user)) users = ServerReference.create([])
58 private launch_date = Date.now()
59
60 /** Page **/
61
d34acb4 @Aqua-Ye various cosmetic improvements
authored
62 watch_button =
63 <iframe src="http://markdotto.github.com/github-buttons/github-btn.html?user={GITHUB_USER}&repo={GITHUB_REPO}&type=watch&count=true&size=large"
64 allowtransparency="true" frameborder="0" scrolling="0" width="146px" height="30px"></iframe>
65
66 fork_button =
67 <iframe src="http://markdotto.github.com/github-buttons/github-btn.html?user={GITHUB_USER}&repo={GITHUB_REPO}&type=fork&count=true&size=large"
68 allowtransparency="true" frameborder="0" scrolling="0" width="146px" height="30px"></iframe>
69
f2a5736 @Aqua-Ye added media type and wip on file upload / sharing
authored
70 function build_page(content) {
1fc268e @Aqua-Ye [clean/enhance/improve] a lot of changes, mainly clean-up :
authored
71 <div id=#header>
f76f439 [enhance] header, homepage and chatroom structure improvement
Ida Swarczewskaja authored
72 <h2 class="pull-left">OpaChat</h2>
f2a5736 @Aqua-Ye added media type and wip on file upload / sharing
authored
73 <div class="buttons pull-right">
74 {watch_button}
75 {fork_button}
76 </div>
1fc268e @Aqua-Ye [clean/enhance/improve] a lot of changes, mainly clean-up :
authored
77 </div>
d34acb4 @Aqua-Ye various cosmetic improvements
authored
78 <div id=#main>{content}</div>
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
79 }
56e4e7e @Aqua-Ye [enhance] a lot of improvements: focus on input, auto_scroll working,…
authored
80
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
81 /** Connection **/
82
83 server function server_observe(message) {
84 match (message) {
85 case {connection:(user, client_channel)} :
86 ServerReference.update(users, List.add(user, _))
87 Network.broadcast({stats}, room)
88 Session.on_remove(client_channel, function() {
89 server_observe({disconnection:user})
90 })
91 case {disconnection:user} :
92 ServerReference.update(users, List.remove(user, _))
93 Network.broadcast({stats}, room)
94 default: void
95 }
96 }
c3dda6b @Aqua-Ye [perfs] greatly improved XHR requests per message
authored
97
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
98 _ = Network.observe(server_observe, room)
99
100 /** Stats **/
1fc268e @Aqua-Ye [clean/enhance/improve] a lot of changes, mainly clean-up :
authored
101
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
102 server function mem() {
103 System.get_memory_usage()/(1024*1024)
104 }
ac588c1 @Aqua-Ye [improvement] re-added users list + improve XHR
authored
105
2b8bc49 @Aqua-Ye message when users connect/disconnect + css + async
authored
106 server function compute_stats() {
ac588c1 @Aqua-Ye [improvement] re-added users list + improve XHR
authored
107 uptime_duration = Date.between(launch_date, Date.now())
108 uptime = Date.of_duration(uptime_duration)
109 uptime = Date.shift_backward(uptime, Date.to_duration(Date.milliseconds(3600000))) // 1 hour shift
110 (uptime, mem())
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
111 }
112
d34acb4 @Aqua-Ye various cosmetic improvements
authored
113 client @async function update_stats((uptime, mem)) {
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
114 #uptime = <>Uptime: {Date.to_string_time_only(uptime)}</>
115 #memory = <>Memory: {mem} Mo</>
116 }
117
d34acb4 @Aqua-Ye various cosmetic improvements
authored
118 client @async function update_users(nb_users, users) {
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
119 #users = <>Users: {nb_users}</>
120 #user_list = <ul>{users}</ul>
121 }
ac588c1 @Aqua-Ye [improvement] re-added users list + improve XHR
authored
122
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
123 /** Conversation **/
124
f2a5736 @Aqua-Ye added media type and wip on file upload / sharing
authored
125 function source_to_html(source) {
126 match (source) {
127 case {system} : <span class="system"/>
128 case {~user} : <span class="user">{user.name}</span>
129 }
130 }
131
d34acb4 @Aqua-Ye various cosmetic improvements
authored
132 client @async function message_update(stats, list(message) messages) {
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
133 update_stats(stats)
134 List.iter(function(message) {
2b8bc49 @Aqua-Ye message when users connect/disconnect + css + async
authored
135 date = Date.to_formatted_string(Date.default_printer, message.date)
136 time = Date.to_string_time_only(message.date)
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
137 line = <div class="line">
2b8bc49 @Aqua-Ye message when users connect/disconnect + css + async
authored
138 <span class="date" title="{date}">{time}</span>
f2a5736 @Aqua-Ye added media type and wip on file upload / sharing
authored
139 { source_to_html(message.source) }
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
140 <span class="message">{message.text}</span>
141 </div>
142 #conversation =+ line
143 }, messages)
144 Dom.scroll_to_bottom(#conversation)
145 }
146
f2a5736 @Aqua-Ye added media type and wip on file upload / sharing
authored
147 client @async function media_update(stats, list(media) medias) {
148 update_stats(stats)
149 List.iter(function(media) {
150 date = Date.to_formatted_string(Date.default_printer, media.date)
151 time = Date.to_string_time_only(media.date)
152 media_parser = parser {
153 case "image/" .*: {image}
154 case "audio/" .*: {audio}
155 case "video/" .*: {video}
156 }
157 line = <div class="line">
158 <span class="date" title="{date}">{time}</span>
159 { source_to_html(media.source) }
160 { match (Parser.try_parse(media_parser, media.mimetype)) {
161 case {some:{image}}:
162 <img src="{media.src}" alt="{media.name}"/>
163 case {some:{audio}}:
164 <audio src="{media.src}"
165 controls="controls"
166 type="{media.mimetype}"
167 preload="auto">
168 Your browser does not support the audio tag!
169 </audio>
170 case {some:{video}}:
171 <video src="{media.src}"
172 controls="controls"
173 preload="auto"
174 type="{media.name}">
175 Your browser does not support the video tag!
176 </video>
177 default:
178 <span class="media {media.mimetype}"> is sharing a file :
179 <a target="_blank" href="{media.src}"
180 draggable="true"
181 data-downloadurl="{media.mimetype}:{media.name}:{media.src}">{media.name}</a>
182 </span>
183 } }
184 </div>
185 #conversation =+ line
186 }, medias)
187 Dom.scroll_to_bottom(#conversation)
188 }
189
2b8bc49 @Aqua-Ye message when users connect/disconnect + css + async
authored
190 exposed @async function server_broadcast(user, text) {
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
191 message = {source:user, ~text, date:Date.now()}
192 /history[?] <- message
193 Network.broadcast({~message}, room)
194 }
195
2b8bc49 @Aqua-Ye message when users connect/disconnect + css + async
authored
196 client @async function broadcast(user, _) {
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
197 _ = server_broadcast(user, Dom.get_value(#entry))
198 Dom.clear_value(#entry)
199 }
200
201 server function client_observe(msg) {
202 match (msg) {
203 case {~message} :
204 message_update(compute_stats(), [message])
205 case {stats} :
206 update_stats(compute_stats())
207 users = ServerReference.get(users)
208 users_html_list = List.fold(function(elt, acc) {
209 <><li>{elt.name}</li>{acc}</>
210 }, users, <></>)
211 update_users(List.length(users), users_html_list)
2b8bc49 @Aqua-Ye message when users connect/disconnect + css + async
authored
212 case {connection:(user, _)} :
213 message = {
214 source: {system},
215 text : "{user.name} joined the room",
216 date : Date.now(),
217 }
218 message_update(compute_stats(), [message])
219 case {disconnection:user} :
220 message = {
221 source: {system},
222 text : "{user.name} left the room",
223 date : Date.now(),
224 }
225 message_update(compute_stats(), [message])
f2a5736 @Aqua-Ye added media type and wip on file upload / sharing
authored
226 case {~media} :
227 media_update(compute_stats(), [media])
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
228 default : void
229 }
230 }
231
f2a5736 @Aqua-Ye added media type and wip on file upload / sharing
authored
232 server function file_uploaded(user)(name, mimetype, key) {
233 media = {
234 source: {~user},
235 ~name,
236 src: "/file/{key}",
237 ~mimetype,
238 date: Date.now(),
239 }
240 Network.broadcast({~media}, room)
241 }
242
df6df94 @Aqua-Ye some comments
authored
243 // Init the client from the server
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
244 server function init_client(user, client_channel) {
df6df94 @Aqua-Ye some comments
authored
245 // Observe client
113e829 @Aqua-Ye [enhance] better network usage, and client connection/disconnection +…
authored
246 obs = Network.observe(client_observe, room)
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
247 Network.broadcast({connection:(user, client_channel)}, room)
df6df94 @Aqua-Ye some comments
authored
248 // Observe disconnection
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
249 Dom.bind_beforeunload_confirmation(function(_) {
250 Network.broadcast({disconnection:user}, room)
251 Network.unobserve(obs)
252 none
253 })
df6df94 @Aqua-Ye some comments
authored
254 // Send NB_LAST_MSGS messages
1fc268e @Aqua-Ye [clean/enhance/improve] a lot of changes, mainly clean-up :
authored
255 history_list = IntMap.To.val_list(/history)
256 len = List.length(history_list)
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
257 history = List.drop(len-NB_LAST_MSGS, history_list)
258 message_update(compute_stats(), history)
df6df94 @Aqua-Ye some comments
authored
259 // Initialize OpaShare
260 OpaShare.init(file_uploaded(user))
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
261 }
262
263 server @async function enter_chat(user_name, client_channel) {
264 user = {
265 id: Random.int(max_int),
266 name: user_name
267 }
f2a5736 @Aqua-Ye added media type and wip on file upload / sharing
authored
268 send = broadcast({~user}, _)
269 // #Body is the default body id in Opa
d34acb4 @Aqua-Ye various cosmetic improvements
authored
270 #Body = build_page(
271 <div id=#sidebar>
f76f439 [enhance] header, homepage and chatroom structure improvement
Ida Swarczewskaja authored
272 <h4>Users online</h4>
59f9691 @Aqua-Ye started to implement a file sharing feature
authored
273 <div id=#user_list/>
274 {OpaShare.html()}
d34acb4 @Aqua-Ye various cosmetic improvements
authored
275 </div>
70c6738 @Aqua-Ye clean
authored
276 <div id=#content
8a0d81f @Aqua-Ye upgrade to bootstrap 2.0
authored
277 onready={function(_){init_client(user, client_channel)}}>
f76f439 [enhance] header, homepage and chatroom structure improvement
Ida Swarczewskaja authored
278 <div id=#stats><div id=#users/><div id=#uptime/><div id=#memory/></div>
70c6738 @Aqua-Ye clean
authored
279 <div id=#conversation/>
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
280 <div id=#chatbar>
281 <input id=#entry
70c6738 @Aqua-Ye clean
authored
282 autofocus="autofocus"
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
283 onready={function(_){Dom.give_focus(#entry)}}
f2a5736 @Aqua-Ye added media type and wip on file upload / sharing
authored
284 onnewline={send}
285 x-webkit-speech="x-webkit-speech"/>
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
286 </div>
d34acb4 @Aqua-Ye various cosmetic improvements
authored
287 </div>
1fc268e @Aqua-Ye [clean/enhance/improve] a lot of changes, mainly clean-up :
authored
288 )
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
289 }
1fc268e @Aqua-Ye [clean/enhance/improve] a lot of changes, mainly clean-up :
authored
290
70c6738 @Aqua-Ye clean
authored
291 client @async function join(_) {
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
292 name = Dom.get_value(#name)
ac588c1 @Aqua-Ye [improvement] re-added users list + improve XHR
authored
293 client_channel = Session.make_callback(ignore)
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
294 enter_chat(name, client_channel)
295 }
296
79a3cc9 @Aqua-Ye included some headers
authored
297 headers =
298 Xhtml.of_string_unsafe("
299 <!--[if lt IE 9]>
300 <script src=\"//html5shiv.googlecode.com/svn/trunk/html5.js\"></script>
301 <![endif]-->") <+>
302 <meta charset="utf-8"></meta>
303 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"></meta>
304 <meta name="viewport" content="width=device-width,initial-scale=1"></meta>
305
306 // Start page
70c6738 @Aqua-Ye clean
authored
307 server function start() {
f2a5736 @Aqua-Ye added media type and wip on file upload / sharing
authored
308 page = build_page(
f76f439 [enhance] header, homepage and chatroom structure improvement
Ida Swarczewskaja authored
309 <h4>A real-time web chat built in Opa.</h4>
8a0d81f @Aqua-Ye upgrade to bootstrap 2.0
authored
310 <div id=#login class="form-inline">
311 <input id=#name
312 placeholder="Name"
313 autofocus="autofocus"
314 onready={function(_){Dom.give_focus(#name)}}
315 onnewline={join}/>
316 <button class="btn primary"
f2a5736 @Aqua-Ye added media type and wip on file upload / sharing
authored
317 onclick={join}>Join</button>
8a0d81f @Aqua-Ye upgrade to bootstrap 2.0
authored
318 </div>
d34acb4 @Aqua-Ye various cosmetic improvements
authored
319 )
f2a5736 @Aqua-Ye added media type and wip on file upload / sharing
authored
320 Resource.full_page_with_doctype(
321 "OpaChat - a real-time web chat built in Opa",
322 {html5},
79a3cc9 @Aqua-Ye included some headers
authored
323 page, headers, {success},
f2a5736 @Aqua-Ye added media type and wip on file upload / sharing
authored
324 []
325 )
326 }
327
df6df94 @Aqua-Ye some comments
authored
328 // Parse URL
f2a5736 @Aqua-Ye added media type and wip on file upload / sharing
authored
329 url_parser = parser {
330 case "/file/" key=Rule.integer:
331 match (OpaShare.get(key)) {
332 case {some:file}:
333 Resource.binary(file.content, file.mimetype)
334 |> Resource.add_header(_, {content_disposition:{attachment:file.name}})
335 default: start()
336 }
337 case (.*): start()
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
338 }
1fc268e @Aqua-Ye [clean/enhance/improve] a lot of changes, mainly clean-up :
authored
339
df6df94 @Aqua-Ye some comments
authored
340 // Start the server
8a41842 @Aqua-Ye switching to js-like syntax + clean/rewrite
authored
341 Server.start(Server.http, [
8a0d81f @Aqua-Ye upgrade to bootstrap 2.0
authored
342 { resources : @static_resource_directory("resources") }, // include resources directory
343 { register : [
344 "/resources/css/bootstrap.min.css",
2174ebe @Aqua-Ye upgraded to bootstrap 2.0.1, and include responsive css
authored
345 "/resources/css/bootstrap-responsive.min.css",
f2a5736 @Aqua-Ye added media type and wip on file upload / sharing
authored
346 "/resources/css/style.css",
347 ] }, // include CSS in headers
df6df94 @Aqua-Ye some comments
authored
348 { custom : url_parser } // URL parser
8a0d81f @Aqua-Ye upgrade to bootstrap 2.0
authored
349 ])
Something went wrong with that request. Please try again.