PageRenderTime 76ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/chrome/content/lib/forge/flash/SocketPool.as

http://github.com/stesie/geierlein
ActionScript | 754 lines | 486 code | 73 blank | 195 comment | 31 complexity | f34aa60196db3e3f3554d8dc47e0a883 MD5 | raw file
Possible License(s): AGPL-3.0, GPL-2.0
  1. /*
  2. * Copyright (c) 2009-2010 Digital Bazaar, Inc. All rights reserved.
  3. *
  4. * @author Dave Longley
  5. */
  6. package
  7. {
  8. import flash.display.Sprite;
  9. /**
  10. * A SocketPool is a flash object that can be embedded in a web page to
  11. * allow javascript access to pools of Sockets.
  12. *
  13. * Javascript can create a pool and then as many Sockets as it desires. Each
  14. * Socket will be assigned a unique ID that allows continued javascript
  15. * access to it. There is no limit on the number of persistent socket
  16. * connections.
  17. */
  18. public class SocketPool extends Sprite
  19. {
  20. import flash.events.Event;
  21. import flash.events.EventDispatcher;
  22. import flash.errors.IOError;
  23. import flash.events.IOErrorEvent;
  24. import flash.events.ProgressEvent;
  25. import flash.events.SecurityErrorEvent;
  26. import flash.events.TextEvent;
  27. import flash.external.ExternalInterface;
  28. import flash.net.SharedObject;
  29. import flash.system.Security;
  30. import flash.utils.ByteArray;
  31. import mx.utils.Base64Decoder;
  32. import mx.utils.Base64Encoder;
  33. // a map of ID to Socket
  34. private var mSocketMap:Object;
  35. // a counter for Socket IDs (Note: assumes there will be no overflow)
  36. private var mNextId:uint;
  37. // an event dispatcher for sending events to javascript
  38. private var mEventDispatcher:EventDispatcher;
  39. /**
  40. * Creates a new, unitialized SocketPool.
  41. *
  42. * @throws Error - if no external interface is available to provide
  43. * javascript access.
  44. */
  45. public function SocketPool()
  46. {
  47. if(!ExternalInterface.available)
  48. {
  49. trace("ExternalInterface is not available");
  50. throw new Error(
  51. "Flash's ExternalInterface is not available. This is a " +
  52. "requirement of SocketPool and therefore, it will be " +
  53. "unavailable.");
  54. }
  55. else
  56. {
  57. try
  58. {
  59. // set up javascript access:
  60. // initializes/cleans up the SocketPool
  61. ExternalInterface.addCallback("init", init);
  62. ExternalInterface.addCallback("cleanup", cleanup);
  63. // creates/destroys a socket
  64. ExternalInterface.addCallback("create", create);
  65. ExternalInterface.addCallback("destroy", destroy);
  66. // connects/closes a socket
  67. ExternalInterface.addCallback("connect", connect);
  68. ExternalInterface.addCallback("close", close);
  69. // checks for a connection
  70. ExternalInterface.addCallback("isConnected", isConnected);
  71. // sends/receives data over the socket
  72. ExternalInterface.addCallback("send", send);
  73. ExternalInterface.addCallback("receive", receive);
  74. // gets the number of bytes available on a socket
  75. ExternalInterface.addCallback(
  76. "getBytesAvailable", getBytesAvailable);
  77. // add a callback for subscribing to socket events
  78. ExternalInterface.addCallback("subscribe", subscribe);
  79. // add callbacks for deflate/inflate
  80. ExternalInterface.addCallback("deflate", deflate);
  81. ExternalInterface.addCallback("inflate", inflate);
  82. // add callbacks for local disk storage
  83. ExternalInterface.addCallback("setItem", setItem);
  84. ExternalInterface.addCallback("getItem", getItem);
  85. ExternalInterface.addCallback("removeItem", removeItem);
  86. ExternalInterface.addCallback("clearItems", clearItems);
  87. // socket pool is now ready
  88. ExternalInterface.call("window.forge.socketPool.ready");
  89. }
  90. catch(e:Error)
  91. {
  92. log("error=" + e.errorID + "," + e.name + "," + e.message);
  93. throw e;
  94. }
  95. log("ready");
  96. }
  97. }
  98. /**
  99. * A debug logging function.
  100. *
  101. * @param obj the string or error to log.
  102. */
  103. CONFIG::debugging
  104. private function log(obj:Object):void
  105. {
  106. if(obj is String)
  107. {
  108. var str:String = obj as String;
  109. ExternalInterface.call("console.log", "SocketPool", str);
  110. }
  111. else if(obj is Error)
  112. {
  113. var e:Error = obj as Error;
  114. log("error=" + e.errorID + "," + e.name + "," + e.message);
  115. }
  116. }
  117. CONFIG::release
  118. private function log(obj:Object):void
  119. {
  120. // log nothing in release mode
  121. }
  122. /**
  123. * Called by javascript to initialize this SocketPool.
  124. *
  125. * @param options:
  126. * marshallExceptions: true to pass exceptions to and from
  127. * javascript.
  128. */
  129. private function init(... args):void
  130. {
  131. log("init()");
  132. // get options from first argument
  133. var options:Object = args.length > 0 ? args[0] : null;
  134. // create socket map, set next ID, and create event dispatcher
  135. mSocketMap = new Object();
  136. mNextId = 1;
  137. mEventDispatcher = new EventDispatcher();
  138. // enable marshalling exceptions if appropriate
  139. if(options != null &&
  140. "marshallExceptions" in options &&
  141. options.marshallExceptions === true)
  142. {
  143. try
  144. {
  145. // Note: setting marshallExceptions in IE, even inside of a
  146. // try-catch block will terminate flash. Don't set this on IE.
  147. ExternalInterface.marshallExceptions = true;
  148. }
  149. catch(e:Error)
  150. {
  151. log(e);
  152. }
  153. }
  154. log("init() done");
  155. }
  156. /**
  157. * Called by javascript to clean up a SocketPool.
  158. */
  159. private function cleanup():void
  160. {
  161. log("cleanup()");
  162. mSocketMap = null;
  163. mNextId = 1;
  164. mEventDispatcher = null;
  165. log("cleanup() done");
  166. }
  167. /**
  168. * Handles events.
  169. *
  170. * @param e the event to handle.
  171. */
  172. private function handleEvent(e:Event):void
  173. {
  174. // dispatch socket event
  175. var message:String = (e is TextEvent) ? (e as TextEvent).text : null;
  176. mEventDispatcher.dispatchEvent(
  177. new SocketEvent(e.type, e.target as PooledSocket, message));
  178. }
  179. /**
  180. * Called by javascript to create a Socket.
  181. *
  182. * @return the Socket ID.
  183. */
  184. private function create():String
  185. {
  186. log("create()");
  187. // create a Socket
  188. var id:String = "" + mNextId++;
  189. var s:PooledSocket = new PooledSocket();
  190. s.id = id;
  191. s.addEventListener(Event.CONNECT, handleEvent);
  192. s.addEventListener(Event.CLOSE, handleEvent);
  193. s.addEventListener(ProgressEvent.SOCKET_DATA, handleEvent);
  194. s.addEventListener(IOErrorEvent.IO_ERROR, handleEvent);
  195. s.addEventListener(SecurityErrorEvent.SECURITY_ERROR, handleEvent);
  196. mSocketMap[id] = s;
  197. log("socket " + id + " created");
  198. log("create() done");
  199. return id;
  200. }
  201. /**
  202. * Called by javascript to clean up a Socket.
  203. *
  204. * @param id the ID of the Socket to clean up.
  205. */
  206. private function destroy(id:String):void
  207. {
  208. log("destroy(" + id + ")");
  209. if(id in mSocketMap)
  210. {
  211. // remove Socket
  212. delete mSocketMap[id];
  213. log("socket " + id + " destroyed");
  214. }
  215. log("destroy(" + id + ") done");
  216. }
  217. /**
  218. * Connects the Socket with the given ID to the given host and port,
  219. * using the given socket policy port.
  220. *
  221. * @param id the ID of the Socket.
  222. * @param host the host to connect to.
  223. * @param port the port to connect to.
  224. * @param spPort the security policy port to use, 0 to use a url.
  225. * @param spUrl the http URL to the policy file to use, null for default.
  226. */
  227. private function connect(
  228. id:String, host:String, port:uint, spPort:uint,
  229. spUrl:String = null):void
  230. {
  231. log("connect(" +
  232. id + "," + host + "," + port + "," + spPort + "," + spUrl + ")");
  233. if(id in mSocketMap)
  234. {
  235. // get the Socket
  236. var s:PooledSocket = mSocketMap[id];
  237. // load socket policy file
  238. // (permits socket access to backend)
  239. if(spPort !== 0)
  240. {
  241. spUrl = "xmlsocket://" + host + ":" + spPort;
  242. log("using cross-domain url: " + spUrl);
  243. Security.loadPolicyFile(spUrl);
  244. }
  245. else if(spUrl !== null && typeof(spUrl) !== undefined)
  246. {
  247. log("using cross-domain url: " + spUrl);
  248. Security.loadPolicyFile(spUrl);
  249. }
  250. else
  251. {
  252. log("not loading any cross-domain url");
  253. }
  254. // connect
  255. s.connect(host, port);
  256. }
  257. else
  258. {
  259. // no such socket
  260. log("socket " + id + " does not exist");
  261. }
  262. log("connect(" + id + ") done");
  263. }
  264. /**
  265. * Closes the Socket with the given ID.
  266. *
  267. * @param id the ID of the Socket.
  268. */
  269. private function close(id:String):void
  270. {
  271. log("close(" + id + ")");
  272. if(id in mSocketMap)
  273. {
  274. // close the Socket
  275. var s:PooledSocket = mSocketMap[id];
  276. if(s.connected)
  277. {
  278. s.close();
  279. }
  280. }
  281. else
  282. {
  283. // no such socket
  284. log("socket " + id + " does not exist");
  285. }
  286. log("close(" + id + ") done");
  287. }
  288. /**
  289. * Determines if the Socket with the given ID is connected or not.
  290. *
  291. * @param id the ID of the Socket.
  292. *
  293. * @return true if the socket is connected, false if not.
  294. */
  295. private function isConnected(id:String):Boolean
  296. {
  297. var rval:Boolean = false;
  298. log("isConnected(" + id + ")");
  299. if(id in mSocketMap)
  300. {
  301. // check the Socket
  302. var s:PooledSocket = mSocketMap[id];
  303. rval = s.connected;
  304. }
  305. else
  306. {
  307. // no such socket
  308. log("socket " + id + " does not exist");
  309. }
  310. log("isConnected(" + id + ") done");
  311. return rval;
  312. }
  313. /**
  314. * Writes bytes to a Socket.
  315. *
  316. * @param id the ID of the Socket.
  317. * @param bytes the string of base64-encoded bytes to write.
  318. *
  319. * @return true on success, false on failure.
  320. */
  321. private function send(id:String, bytes:String):Boolean
  322. {
  323. var rval:Boolean = false;
  324. log("send(" + id + ")");
  325. if(id in mSocketMap)
  326. {
  327. // write bytes to socket
  328. var s:PooledSocket = mSocketMap[id];
  329. try
  330. {
  331. var b64:Base64Decoder = new Base64Decoder();
  332. b64.decode(bytes);
  333. var b:ByteArray = b64.toByteArray();
  334. s.writeBytes(b, 0, b.length);
  335. s.flush();
  336. rval = true;
  337. }
  338. catch(e:IOError)
  339. {
  340. log(e);
  341. // dispatch IO error event
  342. mEventDispatcher.dispatchEvent(new SocketEvent(
  343. IOErrorEvent.IO_ERROR, s, e.message));
  344. if(s.connected)
  345. {
  346. s.close();
  347. }
  348. }
  349. }
  350. else
  351. {
  352. // no such socket
  353. log("socket " + id + " does not exist");
  354. }
  355. log("send(" + id + ") done");
  356. return rval;
  357. }
  358. /**
  359. * Receives bytes from a Socket.
  360. *
  361. * @param id the ID of the Socket.
  362. * @param count the maximum number of bytes to receive.
  363. *
  364. * @return an object with 'rval' set to the received bytes,
  365. * base64-encoded, or set to null on error.
  366. */
  367. private function receive(id:String, count:uint):Object
  368. {
  369. var rval:String = null;
  370. log("receive(" + id + "," + count + ")");
  371. if(id in mSocketMap)
  372. {
  373. // only read what is available
  374. var s:PooledSocket = mSocketMap[id];
  375. if(count > s.bytesAvailable)
  376. {
  377. count = s.bytesAvailable;
  378. }
  379. try
  380. {
  381. // read bytes from socket
  382. var b:ByteArray = new ByteArray();
  383. s.readBytes(b, 0, count);
  384. b.position = 0;
  385. var b64:Base64Encoder = new Base64Encoder();
  386. b64.insertNewLines = false;
  387. b64.encodeBytes(b, 0, b.length);
  388. rval = b64.toString();
  389. }
  390. catch(e:IOError)
  391. {
  392. log(e);
  393. // dispatch IO error event
  394. mEventDispatcher.dispatchEvent(new SocketEvent(
  395. IOErrorEvent.IO_ERROR, s, e.message));
  396. if(s.connected)
  397. {
  398. s.close();
  399. }
  400. }
  401. }
  402. else
  403. {
  404. // no such socket
  405. log("socket " + id + " does not exist");
  406. }
  407. log("receive(" + id + "," + count + ") done");
  408. return {rval: rval};
  409. }
  410. /**
  411. * Gets the number of bytes available from a Socket.
  412. *
  413. * @param id the ID of the Socket.
  414. *
  415. * @return the number of available bytes.
  416. */
  417. private function getBytesAvailable(id:String):uint
  418. {
  419. var rval:uint = 0;
  420. log("getBytesAvailable(" + id + ")");
  421. if(id in mSocketMap)
  422. {
  423. var s:PooledSocket = mSocketMap[id];
  424. rval = s.bytesAvailable;
  425. }
  426. else
  427. {
  428. // no such socket
  429. log("socket " + id + " does not exist");
  430. }
  431. log("getBytesAvailable(" + id +") done");
  432. return rval;
  433. }
  434. /**
  435. * Registers a javascript function as a callback for an event.
  436. *
  437. * @param eventType the type of event (socket event types).
  438. * @param callback the name of the callback function.
  439. */
  440. private function subscribe(eventType:String, callback:String):void
  441. {
  442. log("subscribe(" + eventType + "," + callback + ")");
  443. switch(eventType)
  444. {
  445. case Event.CONNECT:
  446. case Event.CLOSE:
  447. case IOErrorEvent.IO_ERROR:
  448. case SecurityErrorEvent.SECURITY_ERROR:
  449. case ProgressEvent.SOCKET_DATA:
  450. {
  451. log(eventType + " => " + callback);
  452. mEventDispatcher.addEventListener(
  453. eventType, function(event:SocketEvent):void
  454. {
  455. log("event dispatched: " + eventType);
  456. // build event for javascript
  457. var e:Object = new Object();
  458. e.id = event.socket ? event.socket.id : 0;
  459. e.type = eventType;
  460. if(event.socket && event.socket.connected)
  461. {
  462. e.bytesAvailable = event.socket.bytesAvailable;
  463. }
  464. else
  465. {
  466. e.bytesAvailable = 0;
  467. }
  468. if(event.message)
  469. {
  470. e.message = event.message;
  471. }
  472. // send event to javascript
  473. ExternalInterface.call(callback, e);
  474. });
  475. break;
  476. }
  477. default:
  478. throw new ArgumentError(
  479. "Could not subscribe to event. " +
  480. "Invalid event type specified: " + eventType);
  481. }
  482. log("subscribe(" + eventType + "," + callback + ") done");
  483. }
  484. /**
  485. * Deflates the given data.
  486. *
  487. * @param data the base64-encoded data to deflate.
  488. *
  489. * @return an object with 'rval' set to deflated data, base64-encoded.
  490. */
  491. private function deflate(data:String):Object
  492. {
  493. log("deflate");
  494. var b64d:Base64Decoder = new Base64Decoder();
  495. b64d.decode(data);
  496. var b:ByteArray = b64d.toByteArray();
  497. b.compress();
  498. b.position = 0;
  499. var b64e:Base64Encoder = new Base64Encoder();
  500. b64e.insertNewLines = false;
  501. b64e.encodeBytes(b, 0, b.length);
  502. log("deflate done");
  503. return {rval: b64e.toString()};
  504. }
  505. /**
  506. * Inflates the given data.
  507. *
  508. * @param data the base64-encoded data to inflate.
  509. *
  510. * @return an object with 'rval' set to the inflated data,
  511. * base64-encoded, null on error.
  512. */
  513. private function inflate(data:String):Object
  514. {
  515. log("inflate");
  516. var rval:Object = {rval: null};
  517. try
  518. {
  519. var b64d:Base64Decoder = new Base64Decoder();
  520. b64d.decode(data);
  521. var b:ByteArray = b64d.toByteArray();
  522. b.uncompress();
  523. b.position = 0;
  524. var b64e:Base64Encoder = new Base64Encoder();
  525. b64e.insertNewLines = false;
  526. b64e.encodeBytes(b, 0, b.length);
  527. rval.rval = b64e.toString();
  528. }
  529. catch(e:Error)
  530. {
  531. log(e);
  532. rval.error = {
  533. id: e.errorID,
  534. name: e.name,
  535. message: e.message
  536. };
  537. }
  538. log("inflate done");
  539. return rval;
  540. }
  541. /**
  542. * Stores an item with a key and arbitrary base64-encoded data on local
  543. * disk.
  544. *
  545. * @param key the key for the item.
  546. * @param data the base64-encoded item data.
  547. * @param storeId the storage ID to use, defaults to "forge.storage".
  548. *
  549. * @return an object with rval set to true on success, false on failure
  550. * with error included.
  551. */
  552. private function setItem(
  553. key:String, data:String, storeId:String = "forge.storage"):Object
  554. {
  555. var rval:Object = {rval: false};
  556. try
  557. {
  558. var store:SharedObject = SharedObject.getLocal(storeId);
  559. if(!('keys' in store.data))
  560. {
  561. store.data.keys = {};
  562. }
  563. store.data.keys[key] = data;
  564. store.flush();
  565. rval.rval = true;
  566. }
  567. catch(e:Error)
  568. {
  569. log(e);
  570. rval.error = {
  571. id: e.errorID,
  572. name: e.name,
  573. message: e.message
  574. };
  575. }
  576. return rval;
  577. }
  578. /**
  579. * Gets an item from the local disk.
  580. *
  581. * @param key the key for the item.
  582. * @param storeId the storage ID to use, defaults to "forge.storage".
  583. *
  584. * @return an object with rval set to the item data (which may be null),
  585. * check for error object if null.
  586. */
  587. private function getItem(
  588. key:String, storeId:String = "forge.storage"):Object
  589. {
  590. var rval:Object = {rval: null};
  591. try
  592. {
  593. var store:SharedObject = SharedObject.getLocal(storeId);
  594. if('keys' in store.data && key in store.data.keys)
  595. {
  596. rval.rval = store.data.keys[key];
  597. }
  598. }
  599. catch(e:Error)
  600. {
  601. log(e);
  602. rval.error = {
  603. id: e.errorID,
  604. name: e.name,
  605. message: e.message
  606. };
  607. }
  608. return rval;
  609. }
  610. /**
  611. * Removes an item from the local disk.
  612. *
  613. * @param key the key for the item.
  614. * @param storeId the storage ID to use, defaults to "forge.storage".
  615. *
  616. * @return an object with rval set to true if removed, false if not.
  617. */
  618. private function removeItem(
  619. key:String, storeId:String = "forge.storage"):Object
  620. {
  621. var rval:Object = {rval: false};
  622. try
  623. {
  624. var store:SharedObject = SharedObject.getLocal(storeId);
  625. if('keys' in store.data && key in store.data.keys)
  626. {
  627. delete store.data.keys[key];
  628. // clean up storage entirely if empty
  629. var empty:Boolean = true;
  630. for(var prop:String in store.data.keys)
  631. {
  632. empty = false;
  633. break;
  634. }
  635. if(empty)
  636. {
  637. store.clear();
  638. }
  639. rval.rval = true;
  640. }
  641. }
  642. catch(e:Error)
  643. {
  644. log(e);
  645. rval.error = {
  646. id: e.errorID,
  647. name: e.name,
  648. message: e.message
  649. };
  650. }
  651. return rval;
  652. }
  653. /**
  654. * Clears an entire store of all of its items.
  655. *
  656. * @param storeId the storage ID to use, defaults to "forge.storage".
  657. *
  658. * @return an object with rval set to true if cleared, false if not.
  659. */
  660. private function clearItems(storeId:String = "forge.storage"):Object
  661. {
  662. var rval:Object = {rval: false};
  663. try
  664. {
  665. var store:SharedObject = SharedObject.getLocal(storeId);
  666. store.clear();
  667. rval.rval = true;
  668. }
  669. catch(e:Error)
  670. {
  671. log(e);
  672. rval.error = {
  673. id: e.errorID,
  674. name: e.name,
  675. message: e.message
  676. };
  677. }
  678. return rval;
  679. }
  680. }
  681. }