]> git.proxmox.com Git - extjs.git/blob - extjs/build/packages/amf/amf-debug.js
add extjs 6.0.1 sources
[extjs.git] / extjs / build / packages / amf / amf-debug.js
1 // @tag enterprise
2 /**
3 * @class Ext.data.amf.Encoder
4 * This class serializes data in the Action Message Format (AMF) format.
5 * It can write simple and complex objects, to be used in conjunction with an
6 * AMF-compliant server.
7 * To encode a byte array, first construct an Encoder, optionally setting the format:
8 *
9 * var encoder = Ext.create('Ext.data.amf.Encoder', {
10 * format: 3
11 * });
12 *
13 * Then use the writer methods to out data to the :
14 *
15 * encoder.writeObject(1);
16 *
17 * And access the data through the #bytes property:
18 * encoder.bytes;
19 *
20 * You can also reset the class to start a new byte array:
21 *
22 * encoder.clear();
23 *
24 * Current limitations:
25 * AMF3 format (format:3)
26 * - writeObject will write out XML object, not legacy XMLDocument objects. A
27 * writeXmlDocument method is provided for explicitly writing XMLDocument
28 * objects.
29 * - Each object is written out explicitly, not using the reference tables
30 * supported by the AMF format. This means the function does NOT support
31 * circular reference objects.
32 * - Array objects: only the numbered indices and data will be written out.
33 * Associative values will be ignored.
34 * - Objects that aren't Arrays, Dates, Strings, Document (XML) or primitive
35 * values will be written out as anonymous objects with dynamic data.
36 * - There's no JavaScript equivalent to the ByteArray type in ActionScript,
37 * hence data will never be searialized as ByteArrays by the writeObject
38 * function. A writeByteArray method is provided for writing out ByteArray objects.
39 *
40 * AMF0 format (format:0)
41 * - Each object is written out explicitly, not using the reference tables
42 * supported by the AMF format. This means the function does NOT support
43 * circular reference objects.
44 * - Array objects: the function always writes an associative array (following
45 * the behavior of flex).
46 * - Objects that aren't Arrays, Dates, Strings, Document (XML) or primitive
47 * values will be written out as anonymous objects.
48 *
49 * For more information on working with AMF data please refer to the
50 * [AMF Guide](#/guide/amf).
51 */
52 Ext.define('Ext.data.amf.Encoder', {
53 alias: 'data.amf.Encoder',
54 config: {
55 format: 3
56 },
57 /**
58 * @property {Array} bytes
59 * @readonly
60 * The constructed byte array.
61 */
62 bytes: [],
63 /**
64 * Creates new Encoder.
65 * @param {Object} config Configuration options
66 */
67 constructor: function(config) {
68 this.initConfig(config);
69 this.clear();
70 },
71 /**
72 * Reset all class states and starts a new empty array for encoding data.
73 * The method generates a new array for encoding, so it's safe to keep a
74 * reference to the old one.
75 */
76 clear: function() {
77 this.bytes = [];
78 },
79 /**
80 * Sets the functions that will correctly serialize for the relevant
81 * protocol version.
82 * @param {Number} protocol_version the protocol version to support
83 */
84 applyFormat: function(protocol_version) {
85 var funcs = {
86 0: {
87 writeUndefined: this.write0Undefined,
88 writeNull: this.write0Null,
89 writeBoolean: this.write0Boolean,
90 writeNumber: this.write0Number,
91 writeString: this.write0String,
92 writeXml: this.write0Xml,
93 writeDate: this.write0Date,
94 writeArray: this.write0Array,
95 writeGenericObject: this.write0GenericObject
96 },
97 3: {
98 writeUndefined: this.write3Undefined,
99 writeNull: this.write3Null,
100 writeBoolean: this.write3Boolean,
101 writeNumber: this.write3Number,
102 writeString: this.write3String,
103 writeXml: this.write3Xml,
104 writeDate: this.write3Date,
105 writeArray: this.write3Array,
106 writeGenericObject: this.write3GenericObject
107 }
108 }[protocol_version];
109 if (funcs) {
110 Ext.apply(this, funcs);
111 return protocol_version;
112 } else {
113 //<debug>
114 Ext.raise("Unsupported AMF format: " + protocol_version + ". Only '3' (AMF3) is supported at this point.");
115 //</debug>
116 return;
117 }
118 },
119 // return nothing
120 /**
121 * Write the appropriate data items to the byte array. Supported types:
122 * - undefined
123 * - null
124 * - boolean
125 * - integer (if AMF3 - limited by 29-bit int, otherwise passed as double)
126 * - double
127 * - UTF-8 string
128 * - XML Document (identified by being instaneof Document. Can be generated with: new DOMParser()).parseFromString(xml, "text/xml");
129 * @param {Object} item A primitive or object to write to the stream
130 */
131 writeObject: function(item) {
132 var t = typeof (item);
133 //Ext.log("Writing " + item + " of type " + t);
134 if (t === "undefined") {
135 this.writeUndefined();
136 } else if (item === null) {
137 // can't check type since typeof(null) returns "object"
138 this.writeNull();
139 } else if (Ext.isBoolean(item)) {
140 this.writeBoolean(item);
141 } else if (Ext.isString(item)) {
142 this.writeString(item);
143 } else if (t === "number" || item instanceof Number) {
144 // Can't use Ext.isNumeric since it accepts strings as well
145 this.writeNumber(item);
146 } else if (t === "object") {
147 // Figure out which object this is
148 if (item instanceof Date) {
149 this.writeDate(item);
150 } else if (Ext.isArray(item)) {
151 // this won't catch associative arrays deserialized by the Packet class!
152 this.writeArray(item);
153 } else if (this.isXmlDocument(item)) {
154 this.writeXml(item);
155 } else {
156 // Treat this as a generic object with name/value pairs of data.
157 this.writeGenericObject(item);
158 }
159 } else {
160 //<debug>
161 Ext.log.warn("AMF Encoder: Unknown item type " + t + " can't be written to stream: " + item);
162 }
163 },
164 //</debug>
165 /**
166 * Writes the AMF3 undefined value to the byte array.
167 * @private
168 */
169 write3Undefined: function() {
170 this.writeByte(0);
171 },
172 // AMF3 undefined
173 /**
174 * Writes the AMF0 undefined value to the byte array.
175 * @private
176 */
177 write0Undefined: function() {
178 this.writeByte(6);
179 },
180 // AMF0 undefined
181 /**
182 * Writes the AMF3 null value to the byte array.
183 * @private
184 */
185 write3Null: function() {
186 this.writeByte(1);
187 },
188 // AMF3 null
189 /**
190 * Writes the AMF0 null value to the byte array.
191 * @private
192 */
193 write0Null: function() {
194 this.writeByte(5);
195 },
196 // AMF0 null
197 /**
198 * Writes the appropriate AMF3 boolean value to the byte array.
199 * @param {boolean} item The value to write
200 * @private
201 */
202 write3Boolean: function(item) {
203 //<debug>
204 if (typeof (item) !== "boolean") {
205 Ext.log.warn("Encoder: writeBoolean argument is not a boolean. Coercing.");
206 }
207 // </debug>
208 if (item) {
209 this.writeByte(3);
210 } else // AMF3 true
211 {
212 this.writeByte(2);
213 }
214 },
215 // AMF3 false
216 /**
217 * Writes the appropriate AMF0 boolean value to the byte array.
218 * @param {boolean} item The value to write
219 * @private
220 */
221 write0Boolean: function(item) {
222 //<debug>
223 if (typeof (item) !== "boolean") {
224 Ext.log.warn("Encoder: writeBoolean argument is not a boolean. Coercing.");
225 }
226 // </debug>
227 this.writeByte(1);
228 // AMF0 boolean marker
229 if (item) {
230 this.writeByte(1);
231 } else // AMF0 true
232 {
233 this.writeByte(0);
234 }
235 },
236 // AMF0 false
237 /**
238 * Encodes a U29 int, returning a byte array with the encoded number.
239 * @param item - unsigned int value
240 * @private
241 */
242 encode29Int: function(item) {
243 var data = [],
244 // prepare the bytes, then send them to the array
245 num = item,
246 nibble, i;
247 if (num == 0) {
248 return [
249 0
250 ];
251 }
252 // no other data
253 // we have a special case if the number is 4-nibbles in U29 encoding
254 if (num > 2097151) {
255 // last nibble is an 8-bit value
256 nibble = num & 255;
257 data.unshift(nibble);
258 num = num >> 8;
259 }
260 // get all the 7-bit parts ready
261 while (num > 0) {
262 nibble = num & 127;
263 // 7 bits
264 data.unshift(nibble);
265 num = num >> 7;
266 }
267 // now we need to mark each MSb of a 7-bit byte with a 1, except the absolute last one which has a 0.
268 // If there's an 8-bit byte, the 7-bit byte before it is marked with 1 as well.
269 for (i = 0; i < data.length - 1; i++) {
270 data[i] = data[i] | 128;
271 }
272 return data;
273 },
274 /**
275 * Writes a numberic value to the byte array in AMF3 format
276 * @param item A native numeric value, Number instance or one of Infinity, -Infinity or NaN
277 * @private
278 */
279 write3Number: function(item) {
280 var data;
281 var maxInt = 536870911,
282 minSignedInt = -268435455;
283 //<debug>
284 if (typeof (item) !== "number" && !(item instanceof Number)) {
285 Ext.log.warn("Encoder: writeNumber argument is not numeric. Can't coerce.");
286 }
287 // </debug>
288 // switch to the primitive value for handling:
289 if (item instanceof Number) {
290 item = item.valueOf();
291 }
292 // First we need to determine if this is an integer or a float.
293 // AMF3 allows integers between -2^28 < item < 2^29, else they need to be passed as doubles.
294 if (item % 1 === 0 && item >= minSignedInt && item <= maxInt) {
295 // The number has no decimal point and is within bounds. Let's encode it.
296 item = item & maxInt;
297 // get an unsigned value to work with - we only care about 29 bits.
298 data = this.encode29Int(item);
299 // And , mark it as an integer
300 data.unshift(4);
301 // AMF3 integer marker
302 // write it!
303 this.writeBytes(data);
304 } else {
305 data = this.encodeDouble(item);
306 data.unshift(5);
307 // AMF3 double marker
308 this.writeBytes(data);
309 }
310 },
311 /**
312 * Writes a numberic value to the byte array in AMF0 format
313 * @param item A native numeric value, Number instance or one of Infinity, -Infinity or NaN
314 * @private
315 */
316 write0Number: function(item) {
317 var data;
318 //<debug>
319 if (typeof (item) !== "number" && !(item instanceof Number)) {
320 Ext.log.warn("Encoder: writeNumber argument is not numeric. Can't coerce.");
321 }
322 // </debug>
323 // switch to the primitive value for handling:
324 if (item instanceof Number) {
325 item = item.valueOf();
326 }
327 //In AMF0 numbers are always serialized as double-float values.
328 data = this.encodeDouble(item);
329 data.unshift(0);
330 // AMF0 double marker
331 this.writeBytes(data);
332 },
333 /**
334 * Convert a UTF 16 char to a UTF 8 char
335 * @param {Number} c char 16-bit code to convert
336 * @return {Array} byte array with the UTF 8 values
337 */
338 encodeUtf8Char: function(c) {
339 var data = [],
340 val, b, i, marker;
341 //<debug>
342 if (c > 1114111) {
343 //<debug>
344 Ext.raise("UTF 8 char out of bounds");
345 }
346 //</debug>
347 //</debug>
348 if (c <= 127) {
349 // One byte UTF8
350 data.push(c);
351 } else {
352 // Multi-byte UTF8. Figure out how many bytes:
353 if (c <= 2047) {
354 b = 2;
355 } else if (c <= 65535) {
356 b = 3;
357 } else {
358 b = 4;
359 }
360 // encode LSBs of value
361 marker = 128;
362 // MSB marker
363 for (i = 1; i < b; i++) {
364 val = (c & 63) | 128;
365 // lowest 6 bits of number, plus a flag to mark the byte
366 data.unshift(val);
367 c = c >> 6;
368 // drop 6 LSbs
369 marker = (marker >> 1) | 128;
370 }
371 // add one more bit for every byte
372 // the final byte is now ready, but we need to mark it to show how many other bytes follow
373 val = c | marker;
374 data.unshift(val);
375 }
376 return data;
377 },
378 /**
379 * Accepts a string and returns a byte array encoded in UTF-8
380 * @param {String} str String to encode
381 * @return {Array} byte array with string encoded in UTF-8 format
382 * @private
383 */
384 encodeUtf8String: function(str) {
385 var i,
386 utf8Data = [];
387 for (i = 0; i < str.length; i++) {
388 var data = this.encodeUtf8Char(str.charCodeAt(i));
389 Ext.Array.push(utf8Data, data);
390 }
391 return utf8Data;
392 },
393 // quicker conversion, doesn't work in IE:
394 // utf8String = unescape(encodeURIComponent(str)),
395 // utf8Data = [];
396 /**
397 * Encode the length of a UTF-8 string in AMF3 format.
398 * @param {Array} utf8Data byte array with the encoded data
399 * @return {Array} byte array encoding of length
400 *
401 * @private
402 */
403 encode3Utf8StringLen: function(utf8Data) {
404 var len = utf8Data.length,
405 data = [];
406 if (len <= 268435455) {
407 // String is under max allowed length in AMF3
408 // AMF3 strings use the LSb to mark whether it's a string reference or a string value. For now we only pass values:
409 len = len << 1;
410 len = len | 1;
411 // mark as value
412 // push length value to the array
413 data = this.encode29Int(len);
414 } else {
415 //<debug>
416 Ext.raise("UTF8 encoded string too long to serialize to AMF: " + len);
417 }
418 //</debug>
419 return data;
420 },
421 /**
422 * Write an AMF3 UTF-8 string to the byte array
423 * @param {String} item The string to write
424 * @private
425 */
426 write3String: function(item) {
427 //<debug>
428 if (!Ext.isString(item)) {
429 Ext.log.warn("Encoder: writString argument is not a string.");
430 }
431 // </debug>
432 if (item == "") {
433 // special case for the empty string
434 this.writeByte(6);
435 // AMF3 string marker
436 this.writeByte(1);
437 } else // zero length string
438 {
439 // first encode string to UTF-8.
440 var utf8Data = this.encodeUtf8String(item);
441 var lenData = this.encode3Utf8StringLen(utf8Data);
442 // only write encoding once we got here, i.e. length of string is legal
443 this.writeByte(6);
444 // AMF3 string marker, only if we can actually write a string
445 this.writeBytes(lenData);
446 this.writeBytes(utf8Data);
447 }
448 },
449 /**
450 * Encode 16- or 32-bit integers into big-endian (network order) bytes
451 * @param {Number} value the number to encode.
452 * @param {Number} byte_count 2 or 4 byte encoding
453 * @return {Array} byte array with encoded number
454 */
455 encodeXInt: function(value, byte_count) {
456 var data = [],
457 i;
458 for (i = 0; i < byte_count; i++) {
459 data.unshift(value & 255);
460 value = value >> 8;
461 }
462 return data;
463 },
464 /**
465 * Write an AMF0 UTF-8 string to the byte array
466 * @param {String} item The string to write
467 * @private
468 */
469 write0String: function(item) {
470 //<debug>
471 if (!Ext.isString(item)) {
472 Ext.log.warn("Encoder: writString argument is not a string.");
473 }
474 // </debug>
475 if (item == "") {
476 // special case for the empty string
477 this.writeByte(2);
478 // AMF0 short string marker
479 this.writeBytes([
480 0,
481 0
482 ]);
483 } else // zero length string
484 {
485 // first encode string to UTF-8.
486 var utf8Data = this.encodeUtf8String(item);
487 var encoding;
488 var lenData;
489 if (utf8Data.length <= 65535) {
490 // short string
491 encoding = 2;
492 // short string
493 lenData = this.encodeXInt(utf8Data.length, 2);
494 } else {
495 // long string
496 encoding = 12;
497 // long string
498 lenData = this.encodeXInt(utf8Data.length, 4);
499 }
500 this.writeByte(encoding);
501 // Approperiate AMF0 string marker
502 this.writeBytes(lenData);
503 this.writeBytes(utf8Data);
504 }
505 },
506 /**
507 * Writes an XML document in AMF3 format.
508 * @param {Object} xml XML document (type Document typically)
509 * @param {number} amfType Either 0x07 or 0x0B - the AMF3 object type to use
510 * @private
511 */
512 write3XmlWithType: function(xml, amfType) {
513 //<debug>
514 // We accept XML Documents, or strings
515 if (amfType !== 7 && amfType !== 11) {
516 Ext.raise("write XML with unknown AMF3 code: " + amfType);
517 }
518 if (!this.isXmlDocument(xml)) {
519 Ext.log.warn("Encoder: write3XmlWithType argument is not an xml document.");
520 }
521 // </debug>
522 var xmlStr = this.convertXmlToString(xml);
523 if (xmlStr == "") {
524 // special case for the empty string
525 this.writeByte(amfType);
526 // AMF3 XML marker
527 this.writeByte(1);
528 } else // zero length string
529 {
530 // first encode string to UTF-8.
531 var utf8Data = this.encodeUtf8String(xmlStr);
532 var lenData = this.encode3Utf8StringLen(utf8Data);
533 // only write encoding once we got here, i.e. length of string is legal
534 this.writeByte(amfType);
535 // AMF3 XML marker, only if we can actually write the string
536 this.writeBytes(lenData);
537 this.writeBytes(utf8Data);
538 }
539 },
540 /**
541 * Writes an Legacy XMLDocument (ActionScript Legacy XML object) in AMF3
542 * format. Must be called explicitly.
543 * The writeObject method will call writeXml and not writeXmlDocument.
544 * @param {Object} xml XML document (type Document typically) to write
545 */
546 write3XmlDocument: function(xml) {
547 this.write3XmlWithType(xml, 7);
548 },
549 /**
550 * Writes an XML object (ActionScript 3 new XML object) in AMF3 format.
551 * @param {Object} xml XML document (type Document typically) to write
552 * @private
553 */
554 write3Xml: function(xml) {
555 this.write3XmlWithType(xml, 11);
556 },
557 /**
558 * Writes an XMLDocument in AMF0 format.
559 * @param {Object} xml XML document (type Document typically) to write
560 * @private
561 */
562 write0Xml: function(xml) {
563 //<debug>
564 // We accept XML Documents, or strings
565 if (!this.isXmlDocument(xml)) {
566 Ext.log.warn("Encoder: write0Xml argument is not an xml document.");
567 }
568 // </debug>
569 var xmlStr = this.convertXmlToString(xml);
570 this.writeByte(15);
571 // AMF0 XML marker
572 // Always encoded as a long string
573 var utf8Data = this.encodeUtf8String(xmlStr);
574 var lenData = this.encodeXInt(utf8Data.length, 4);
575 this.writeBytes(lenData);
576 this.writeBytes(utf8Data);
577 },
578 /**
579 * Writes a date in AMF3 format.
580 * @param {Date} date the date object
581 * @private
582 */
583 write3Date: function(date) {
584 //<debug>
585 if (!(date instanceof Date)) {
586 Ext.raise("Serializing a non-date object as date: " + date);
587 }
588 //</debug>
589 // For now, we don't use object references to just encode the date.
590 this.writeByte(8);
591 // AMF3 date marker
592 this.writeBytes(this.encode29Int(1));
593 // mark this as a date value - we don't support references yet
594 this.writeBytes(this.encodeDouble(new Number(date)));
595 },
596 /**
597 * Writes a date in AMF0 format.
598 * @param {Date} date the date object
599 * @private
600 */
601 write0Date: function(date) {
602 //<debug>
603 if (!(date instanceof Date)) {
604 Ext.raise("Serializing a non-date object as date: " + date);
605 }
606 //</debug>
607 // For now, we don't use object references to just encode the date.
608 this.writeByte(11);
609 // AMF0 date marker
610 this.writeBytes(this.encodeDouble(new Number(date)));
611 this.writeBytes([
612 0,
613 0
614 ]);
615 },
616 // placeholder for timezone, standard says to keep 0, flash actually writes data here
617 /**
618 * Writes an array in AMF3 format. Only the ordered part of the array use handled.
619 * Unordered parts are ignored (e.g. a["hello"] will not be encoded).
620 * @param {Array} arr the array to serialize.
621 * @private
622 */
623 write3Array: function(arr) {
624 //<debug>
625 if (!Ext.isArray(arr)) {
626 Ext.raise("Serializing a non-array object as array: " + arr);
627 }
628 if (arr.length > 268435455) {
629 Ext.raise("Array size too long to encode in AMF3: " + arr.length);
630 }
631 //</debug>
632 // For now, we don't use object references to just encode the array.
633 this.writeByte(9);
634 // AMF3 array marker
635 // encode ordered part of array's length
636 var len = arr.length;
637 len = len << 1;
638 // right-most bit marks this as size
639 len = len | 1;
640 // mark it a size
641 this.writeBytes(this.encode29Int(len));
642 // The associative part of the array is ignored, so mark it as empty
643 this.writeByte(1);
644 // equivalent to an empty UTF-8 string
645 // now iterate over the array, writing each element
646 Ext.each(arr, function(x) {
647 this.writeObject(x);
648 }, this);
649 },
650 /**
651 * Writes a key-value pair in AMF0 format.
652 * @param {String} key the name of the property
653 * @param {Object} value to write in AMF0 format
654 */
655 write0ObjectProperty: function(key, value) {
656 if (!(key instanceof String) && (typeof (key) !== "string")) {
657 // coerce to a string
658 key = key + "";
659 }
660 // first encode the key to a short UTF-8.
661 var utf8Data = this.encodeUtf8String(key);
662 var lenData;
663 lenData = this.encodeXInt(utf8Data.length, 2);
664 this.writeBytes(lenData);
665 this.writeBytes(utf8Data);
666 // and now write out the object
667 this.writeObject(value);
668 },
669 /**
670 * Writes an associative array in AMF0 format.
671 * @param {Array} arr the array to serialize.
672 * @private
673 */
674 write0Array: function(arr) {
675 var key;
676 //<debug>
677 if (!Ext.isArray(arr)) {
678 Ext.raise("Serializing a non-array object as array: " + arr);
679 }
680 //</debug>
681 /* This writes a strict array, but it seems Flex always writes out associative arrays, so mimic behavior
682
683 // For now, we don't use object references to just encode the array.
684 this.writeByte(0x0A); // AMF0 strict array marker
685
686 // encode ordered part of array's length
687 var len = arr.length;
688 this.writeBytes(this.encodeXInt(len, 4));
689
690 // now iterate over the array, writing each element
691 Ext.each(arr, function(x) {this.writeObject(x);}, this);
692 */
693 // Use ECMA (associative) array type
694 this.writeByte(8);
695 // AMF0 ECMA-array marker
696 // we need to know the length of the array before we write the serialized data
697 // to the array. Better to traverse it twice than to copy all the byte data afterwards
698 var total = 0;
699 for (key in arr) {
700 total++;
701 }
702 // Now write out the length of the array
703 this.writeBytes(this.encodeXInt(total, 4));
704 // then write out the data
705 for (key in arr) {
706 Ext.Array.push(this.write0ObjectProperty(key, arr[key]));
707 }
708 // And finally the object end marker
709 this.writeBytes([
710 0,
711 0,
712 9
713 ]);
714 },
715 /**
716 * Writes a strict-array in AMF0 format. Unordered parts are ignored (e.g.
717 * a["hello"] will not be encoded). This function is included for
718 * completeness and will never be called by writeObject.
719 * @param {Array} arr the array to serialize.
720 */
721 write0StrictArray: function(arr) {
722 //<debug>
723 if (!Ext.isArray(arr)) {
724 Ext.raise("Serializing a non-array object as array: " + arr);
725 }
726 //</debug>
727 // For now, we don't use object references to just encode the array.
728 this.writeByte(10);
729 // AMF0 strict array marker
730 // encode ordered part of array's length
731 var len = arr.length;
732 this.writeBytes(this.encodeXInt(len, 4));
733 // now iterate over the array, writing each element
734 Ext.each(arr, function(x) {
735 this.writeObject(x);
736 }, this);
737 },
738 /**
739 * Write a byte array in AMF3 format. This function is never called directly
740 * by writeObject since there's no way to distinguish a regular array from a
741 * byte array.
742 * @param {Array} arr the object to serialize.
743 */
744 write3ByteArray: function(arr) {
745 //<debug>
746 if (!Ext.isArray(arr)) {
747 Ext.raise("Serializing a non-array object as array: " + arr);
748 }
749 if (arr.length > 268435455) {
750 Ext.raise("Array size too long to encode in AMF3: " + arr.length);
751 }
752 //</debug>
753 this.writeByte(12);
754 // Byte array marker
755 // for now no support for references, so just dump the length and data
756 // encode array's length
757 var len = arr.length;
758 len = len << 1;
759 // right-most bit marks this as size
760 len = len | 1;
761 // mark it a size
762 this.writeBytes(this.encode29Int(len));
763 // and finally, dump the byte data
764 this.writeBytes(arr);
765 },
766 /**
767 * Write an object to the byte array in AMF3 format.
768 * Since we don't have the class information form Flex, the object
769 * is written as an anonymous object.
770 * @param {Array} obj the object to serialize.
771 * @private
772 */
773 write3GenericObject: function(obj) {
774 var name;
775 //<debug>
776 if (!Ext.isObject(obj)) {
777 Ext.raise("Serializing a non-object object: " + obj);
778 }
779 //</debug>
780 // For now, we don't use object references so just encode the object.
781 this.writeByte(10);
782 // AMF3 object marker
783 // The following 29-int is marked as follows (LSb to MSb) to signify a
784 // "U29O-traits":
785 // 1 - LSb marks an object value (1) or an object reference (0) which
786 // is not yet supported.
787 // 1 - trait values (1) or trait references (0) which are not supported
788 // yet.
789 // 0 - AMF3 format (0) or externalizable, i.e. object handles own
790 // serialization (1) which is not supported.
791 // 1 - dynamic (1) or not dynamic (0) object which is not relevant since
792 // we pass all data as dynamic fields.
793 // The reset of the bits signify how many sealed traits the object has.
794 // we pass 0 since all data is passed as dynamic fields
795 var oType = 11;
796 // binary 1011
797 this.writeByte(oType);
798 // Next we pass the class name, which is the empty string for anonymous
799 // objects
800 this.writeByte(1);
801 // Next are the sealed traits (of which we have none)
802 // And now the name / value pairs of dynamic fields
803 for (name in obj) {
804 // Ensure that name is actually a string
805 var newName = new String(name).valueOf();
806 if (newName == "") {
807 //<debug>
808 Ext.raise("Can't encode non-string field name: " + name);
809 }
810 //</debug>
811 var nameData = (this.encodeUtf8String(name));
812 this.writeBytes(this.encode3Utf8StringLen(name));
813 this.writeBytes(nameData);
814 this.writeObject(obj[name]);
815 }
816 // And mark the end of the dynamic field section with the empty string
817 this.writeByte(1);
818 },
819 /**
820 * Write an object to the byte array in AMF0 format.
821 * Since we don't have the class information form Flex, the object
822 * is written as an anonymous object.
823 * @param {Array} obj the object to serialize.
824 * @private
825 */
826 write0GenericObject: function(obj) {
827 var typed, amfType, key;
828 //<debug>
829 if (!Ext.isObject(obj)) {
830 Ext.raise("Serializing a non-object object: " + obj);
831 }
832 //</debug>
833 // For now, we don't use object references so just encode the object.
834 // if the object is typed, the ID changes and we need to send the type ahead of the data
835 typed = !!obj.$flexType;
836 amfType = typed ? 16 : 3;
837 // typed vs. untyped object
838 this.writeByte(amfType);
839 // AMF0 object marker
840 // if typed, send object type
841 if (typed) {
842 this.write0ShortUtf8String(obj.$flexType);
843 }
844 // then write out the data. There's no counter, but other than that it's the same as an ECMA array
845 for (key in obj) {
846 if (key != "$flexType") {
847 Ext.Array.push(this.write0ObjectProperty(key, obj[key]));
848 }
849 }
850 // And finally the object end marker
851 this.writeBytes([
852 0,
853 0,
854 9
855 ]);
856 },
857 /**
858 * Writes a byte to the byte array
859 * @param {number} b Byte to write to the array
860 * @private
861 */
862 writeByte: function(b) {
863 //<debug>
864 if (b < 0 || b > 255) {
865 Ex.Error.raise('ERROR: Value being written outside byte range: ' + b);
866 }
867 //</debug>
868 Ext.Array.push(this.bytes, b);
869 },
870 /**
871 * Writes a byte array to the byte array
872 * @param {number} b Byte array to append to the array
873 * @private
874 */
875 writeBytes: function(b) {
876 var i;
877 //<debug>
878 if (!Ext.isArray(b)) {
879 Ext.raise("Decoder: writeBytes parameter is not an array: " + b);
880 }
881 for (i = 0; i < b.length; i++) {
882 if (b[i] < 0 || b[i] > 255 || !Ext.isNumber(b[i])) {
883 Ext.raise("ERROR: Value " + i + " being written outside byte range: " + b[i]);
884 }
885 }
886 //</debug>
887 Ext.Array.push(this.bytes, b);
888 },
889 /**
890 * Converts an XML Document object to a string.
891 * @param {Object} xml XML document (type Document typically) to convert
892 * @return {String} A string representing the document
893 * @private
894 */
895 convertXmlToString: function(xml) {
896 var str;
897 if (window.XMLSerializer) {
898 // this is not IE, so:
899 str = new window.XMLSerializer().serializeToString(xml);
900 } else {
901 //no XMLSerializer, might be an old version of IE
902 str = xml.xml;
903 }
904 return str;
905 },
906 /**
907 * Tries to determine if an object is an XML document
908 * @param {Object} item to identify
909 * @return {Boolean} true if it's an XML document, false otherwise
910 */
911 isXmlDocument: function(item) {
912 // We can't test if Document is defined since IE just throws an exception. Instead rely on the DOMParser object
913 if (window.DOMParser) {
914 if (Ext.isDefined(item.doctype)) {
915 return true;
916 }
917 }
918 // Otherwise, check if it has an XML field
919 if (Ext.isString(item.xml)) {
920 // and we can get the xml
921 return true;
922 }
923 return false;
924 },
925 /*
926 * The encodeDouble function is derived from code from the typedarray.js library by Linden Research, Inc.
927 *
928
929 Copyright (c) 2010, Linden Research, Inc.
930
931 Permission is hereby granted, free of charge, to any person obtaining a copy
932 of this software and associated documentation files (the "Software"), to deal
933 in the Software without restriction, including without limitation the rights
934 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
935 copies of the Software, and to permit persons to whom the Software is
936 furnished to do so, subject to the following conditions:
937
938 The above copyright notice and this permission notice shall be included in
939 all copies or substantial portions of the Software.
940
941 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
942 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
943 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
944 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
945 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
946 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
947 THE SOFTWARE.
948 */
949 /**
950 * Encodes an IEEE-754 double-precision number.
951 * @param {Number} num the number to encode
952 * @return {Array} byte array containing the encoded number
953 * @private
954 */
955 encodeDouble: function(v) {
956 var ebits = 11,
957 fbits = 52;
958 // double
959 var bias = (1 << (ebits - 1)) - 1,
960 s, e, f, ln, i, bits, str,
961 data = [];
962 // Precalculated values
963 var K_INFINITY = [
964 127,
965 240,
966 0,
967 0,
968 0,
969 0,
970 0,
971 0
972 ],
973 K_NINFINITY = [
974 255,
975 240,
976 0,
977 0,
978 0,
979 0,
980 0,
981 0
982 ],
983 K_NAN = [
984 255,
985 248,
986 0,
987 0,
988 0,
989 0,
990 0,
991 0
992 ];
993 // Compute sign, exponent, fraction
994 if (isNaN(v)) {
995 data = K_NAN;
996 } else if (v === Infinity) {
997 data = K_INFINITY;
998 } else if (v == -Infinity) {
999 data = K_NINFINITY;
1000 } else {
1001 // not a special case, so encode
1002 if (v === 0) {
1003 e = 0;
1004 f = 0;
1005 s = (1 / v === -Infinity) ? 1 : 0;
1006 } else {
1007 s = v < 0;
1008 v = Math.abs(v);
1009 if (v >= Math.pow(2, 1 - bias)) {
1010 // Normalized
1011 ln = Math.min(Math.floor(Math.log(v) / Math.LN2), bias);
1012 e = ln + bias;
1013 f = Math.round(v * Math.pow(2, fbits - ln) - Math.pow(2, fbits));
1014 } else {
1015 // Denormalized
1016 e = 0;
1017 f = Math.round(v / Math.pow(2, 1 - bias - fbits));
1018 }
1019 }
1020 // Pack sign, exponent, fraction
1021 bits = [];
1022 for (i = fbits; i; i -= 1) {
1023 bits.push(f % 2 ? 1 : 0);
1024 f = Math.floor(f / 2);
1025 }
1026 for (i = ebits; i; i -= 1) {
1027 bits.push(e % 2 ? 1 : 0);
1028 e = Math.floor(e / 2);
1029 }
1030 bits.push(s ? 1 : 0);
1031 bits.reverse();
1032 str = bits.join('');
1033 // Bits to bytes
1034 data = [];
1035 while (str.length) {
1036 data.push(parseInt(str.substring(0, 8), 2));
1037 str = str.substring(8);
1038 }
1039 }
1040 return data;
1041 },
1042 /**
1043 * Writes a short UTF8 string preceded with a 16-bit length.
1044 * @param {String} str the string to write
1045 */
1046 write0ShortUtf8String: function(str) {
1047 var utf8Data = this.encodeUtf8String(str),
1048 lenData;
1049 lenData = this.encodeXInt(utf8Data.length, 2);
1050 this.writeBytes(lenData);
1051 this.writeBytes(utf8Data);
1052 },
1053 /**
1054 * Writes an AMF packet to the byte array
1055 * @param {Array} headers the headers to serialize. Each item in the array
1056 * should be an object with three fields:
1057 * name, mustUnderstand, value
1058 * @param {Array} messages the messages to serialize. Each item in the array
1059 * should be an object with three fields:
1060 * targetUri, responseUri, body
1061 */
1062 writeAmfPacket: function(headers, messages) {
1063 var i;
1064 //<debug>
1065 if (this.config.format != 0) {
1066 Ext.raise("Trying to write a packet on an AMF3 Encoder. Only AMF0 is supported!");
1067 }
1068 if (!Ext.isArray(headers)) {
1069 Ext.raise("headers is not an array: " + headers);
1070 }
1071 if (!Ext.isArray(messages)) {
1072 Ext.raise("messages is not an array: " + messages);
1073 }
1074 //</debug>
1075 // Write Packet marker
1076 this.writeBytes([
1077 0,
1078 0
1079 ]);
1080 // AMF 0 version for this packet.
1081 // Write header count
1082 this.writeBytes(this.encodeXInt(headers.length, 2));
1083 // And actual headers
1084 for (i in headers) {
1085 this.writeAmfHeader(headers[i].name, headers[i].mustUnderstand, headers[i].value);
1086 }
1087 // Write message count
1088 this.writeBytes(this.encodeXInt(messages.length, 2));
1089 // And actual messages
1090 for (i in messages) {
1091 this.writeAmfMessage(messages[i].targetUri, messages[i].responseUri, messages[i].body);
1092 }
1093 },
1094 /**
1095 * Write an AMF header to the byte array. AMF headers are always encoded in AMF0.
1096 * @param {String} headerName the header name
1097 * @param {Boolean} mustUnderstand true if the receiver must understand this header or else reject it, false otherwise
1098 * @param {Object} value the value to serialize. Must be an object that can be serialized by AMF
1099 * @private
1100 */
1101 writeAmfHeader: function(headerName, mustUnderstand, value) {
1102 //<debug>
1103 if (this.config.format != 0) {
1104 Ext.raise("Trying to write a header on an AMF3 Encoder. Only AMF0 is supported!");
1105 }
1106 if (!Ext.isString(headerName)) {
1107 Ext.raise("targetURI is not a string: " + targetUri);
1108 }
1109 if ((typeof (mustUnderstand) !== "boolean") && !Ext.isBoolean(mustUnderstand)) {
1110 Ext.raise("mustUnderstand is not a boolean value: " + mustUnderstand);
1111 }
1112 //</debug>
1113 // write header name
1114 this.write0ShortUtf8String(headerName);
1115 // write must understand byte
1116 var mu = mustUnderstand ? 1 : 0;
1117 this.writeByte(mu);
1118 // next write the header length of -1 (undetermined) to the stream
1119 this.writeBytes(this.encodeXInt(-1, 4));
1120 // write value
1121 this.writeObject(value);
1122 },
1123 /**
1124 * Writes an AMF message to the byte array. AMF messages are always encoded in AMF0.
1125 * @param {String} targetUri the class / method to call
1126 * @param {String} responseUri the response should call here
1127 * @param {Array} body the parameters to pass to the called method, wrapped in an array
1128 * @private
1129 */
1130 writeAmfMessage: function(targetUri, responseUri, body) {
1131 //<debug>
1132 if (this.config.format != 0) {
1133 Ext.raise("Trying to write a message on an AMF3 Encoder. Only AMF0 is supported!");
1134 }
1135 if (!Ext.isString(targetUri)) {
1136 Ext.raise("targetURI is not a string: " + targetUri);
1137 }
1138 if (!Ext.isString(responseUri)) {
1139 Ext.raise("targetURI is not a string: " + responseUri);
1140 }
1141 if (!Ext.isArray(body)) {
1142 Ext.raise("body is not an array: " + typeof (body));
1143 }
1144 //</debug>
1145 // write target URI
1146 this.write0ShortUtf8String(targetUri);
1147 // write response URI
1148 this.write0ShortUtf8String(responseUri);
1149 // next write the message length of -1 (undetermined) to the stream
1150 this.writeBytes(this.encodeXInt(-1, 4));
1151 // write the paramters
1152 this.write0StrictArray(body);
1153 }
1154 });
1155
1156 // @tag enterprise
1157 /**
1158 * @class Ext.data.amf.Packet
1159 * This class represents an Action Message Format (AMF) Packet. It contains all
1160 * the logic required to decode an AMF Packet from a byte array.
1161 * To decode a Packet, first construct a Packet:
1162 *
1163 * var packet = Ext.create('Ext.data.amf.Packet');
1164 *
1165 * Then use the decode method to decode an AMF byte array:
1166 *
1167 * packet.decode(bytes);
1168 *
1169 * where "bytes" is a Uint8Array or an array of numbers representing the binary
1170 * AMF data.
1171 *
1172 * To access the decoded data use the #version, #headers, and #messages properties:
1173 *
1174 * console.log(packet.version, packet.headers, packet.messages);
1175 *
1176 * For more information on working with AMF data please refer to the
1177 * [AMF Guide](#/guide/amf).
1178 */
1179 Ext.define('Ext.data.amf.Packet', function() {
1180 var twoPowN52 = Math.pow(2, -52),
1181 twoPow8 = Math.pow(2, 8),
1182 pos = 0,
1183 bytes, strings, objects, traits;
1184 return {
1185 /**
1186 * @property {Array} headers
1187 * @readonly
1188 * The decoded headers. Each header has the following properties:
1189 *
1190 * - `name`: String
1191 * The header name. Typically identifies a remote operation or method to
1192 * be invoked by this context header.
1193 * - `mustUnderstand`: Boolean
1194 * If `true` this flag instructs the endpoint to abort and generate an
1195 * error if the header is not understood.
1196 * - `byteLength`: Number
1197 * If the byte-length of a header is known it can be specified to optimize
1198 * memory allocation at the remote endpoint.
1199 * - `value`: Mixed
1200 * The header value
1201 */
1202 /**
1203 * @property {Array} messages
1204 * @readonly
1205 * The decoded messages. Each message has the following properties:
1206 *
1207 * - `targetURI`: String
1208 * Describes which operation, function, or method is to be remotely
1209 * invoked.
1210 * - `responseURI`: String
1211 * A unique operation name
1212 * - `byteLength`: Number
1213 * Optional byte-length of the message body
1214 * - `body`: Mixed
1215 * The message body
1216 */
1217 /**
1218 * @property {Number} version
1219 * @readonly
1220 * The AMF version number (0 or 3)
1221 */
1222 /**
1223 * Mapping of AMF data types to the names of the methods responsible for
1224 * reading them.
1225 * @private
1226 */
1227 typeMap: {
1228 // AMF0 mapping
1229 0: {
1230 0: 'readDouble',
1231 1: 'readBoolean',
1232 2: 'readAmf0String',
1233 3: 'readAmf0Object',
1234 5: 'readNull',
1235 6: 'readUndefined',
1236 7: 'readReference',
1237 8: 'readEcmaArray',
1238 10: 'readStrictArray',
1239 11: 'readAmf0Date',
1240 12: 'readLongString',
1241 13: 'readUnsupported',
1242 15: 'readAmf0Xml',
1243 16: 'readTypedObject'
1244 },
1245 // AMF3 mapping
1246 3: {
1247 0: 'readUndefined',
1248 1: 'readNull',
1249 2: 'readFalse',
1250 3: 'readTrue',
1251 4: 'readUInt29',
1252 5: 'readDouble',
1253 6: 'readAmf3String',
1254 7: 'readAmf3Xml',
1255 8: 'readAmf3Date',
1256 9: 'readAmf3Array',
1257 10: 'readAmf3Object',
1258 11: 'readAmf3Xml',
1259 12: 'readByteArray'
1260 }
1261 },
1262 /**
1263 * Decodes an AMF btye array and sets the decoded data as the
1264 * Packet's #version, #headers, and #messages properties
1265 * @param {Array} byteArray A byte array containing the encoded AMF data.
1266 * @return {Ext.data.amf.Packet} this AMF Packet
1267 */
1268 decode: function(byteArray) {
1269 var me = this,
1270 headers = me.headers = [],
1271 messages = me.messages = [],
1272 headerCount, messageCount;
1273 pos = 0;
1274 bytes = me.bytes = byteArray;
1275 // The strings array holds references to all of the deserialized
1276 // AMF3 strings for a given header value or message body so that
1277 // repeat instances of the same string can be deserialized by
1278 // reference
1279 strings = me.strings = [];
1280 // The objects array holds references to deserialized objects so
1281 // that repeat occurrences of the same object instance in the byte
1282 // array can be deserialized by reference.
1283 // If version is AMF0 this array holds anonymous objects, typed
1284 // objects, arrays, and ecma-arrays.
1285 // If version is AMF3 this array holds instances of Object, Array, XML,
1286 // XMLDocument, ByteArray, Date, and instances of user defined Classes
1287 objects = me.objects = [];
1288 // The traits array holds references to the "traits" (the
1289 // characteristics of objects that define a strong type such as the
1290 // class name and public member names) of deserialized AMF3 objects
1291 // so that if they are repeated they can be deserialized by reference.
1292 traits = me.traits = [];
1293 // The first two bytes of an AMF packet contain the AMF version
1294 // as an unsigned 16 bit integer.
1295 me.version = me.readUInt(2);
1296 // the next 2 bytes contain the header count
1297 for (headerCount = me.readUInt(2); headerCount--; ) {
1298 headers.push({
1299 name: me.readAmf0String(),
1300 mustUnderstand: me.readBoolean(),
1301 byteLength: me.readUInt(4),
1302 value: me.readValue()
1303 });
1304 // reset references (reference indices are local to each header)
1305 strings = me.strings = [];
1306 objects = me.objects = [];
1307 traits = me.traits = [];
1308 }
1309 // The 2 bytes immediately after the header contain the message count.
1310 for (messageCount = me.readUInt(2); messageCount--; ) {
1311 messages.push({
1312 targetURI: me.readAmf0String(),
1313 responseURI: me.readAmf0String(),
1314 byteLength: me.readUInt(4),
1315 body: me.readValue()
1316 });
1317 // reset references (reference indices are local to each message)
1318 strings = me.strings = [];
1319 objects = me.objects = [];
1320 traits = me.traits = [];
1321 }
1322 // reset the pointer
1323 pos = 0;
1324 // null the bytes array and reference arrays to free up memory.
1325 bytes = strings = objects = traits = me.bytes = me.strings = me.objects = me.traits = null;
1326 return me;
1327 },
1328 /**
1329 * Decodes an AMF3 byte array and that has one value and returns it.
1330 * Note: Destroys previously stored data in this Packet.
1331 * @param {Array} byteArray A byte array containing the encoded AMF data.
1332 * @return {Object} the decoded object
1333 */
1334 decodeValue: function(byteArray) {
1335 var me = this;
1336 bytes = me.bytes = byteArray;
1337 // reset the pointer
1338 pos = 0;
1339 // The first two bytes of an AMF packet contain the AMF version
1340 // as an unsigned 16 bit integer.
1341 me.version = 3;
1342 // The strings array holds references to all of the deserialized
1343 // AMF3 strings for a given header value or message body so that
1344 // repeat instances of the same string can be deserialized by
1345 // reference
1346 strings = me.strings = [];
1347 // The objects array holds references to deserialized objects so
1348 // that repeat occurrences of the same object instance in the byte
1349 // array can be deserialized by reference.
1350 // If version is AMF0 this array holds anonymous objects, typed
1351 // objects, arrays, and ecma-arrays.
1352 // If version is AMF3 this array holds instances of Object, Array, XML,
1353 // XMLDocument, ByteArray, Date, and instances of user defined Classes
1354 objects = me.objects = [];
1355 // The traits array holds references to the "traits" (the
1356 // characteristics of objects that define a strong type such as the
1357 // class name and public member names) of deserialized AMF3 objects
1358 // so that if they are repeated they can be deserialized by reference.
1359 traits = me.traits = [];
1360 // read one value and return it
1361 return me.readValue();
1362 },
1363 /**
1364 * Parses an xml string and returns an xml document
1365 * @private
1366 * @param {String} xml
1367 */
1368 parseXml: function(xml) {
1369 var doc;
1370 if (window.DOMParser) {
1371 doc = (new DOMParser()).parseFromString(xml, "text/xml");
1372 } else {
1373 doc = new ActiveXObject("Microsoft.XMLDOM");
1374 doc.loadXML(xml);
1375 }
1376 return doc;
1377 },
1378 /**
1379 * Reads an AMF0 date from the byte array
1380 * @private
1381 */
1382 readAmf0Date: function() {
1383 var date = new Date(this.readDouble());
1384 // An AMF0 date type ends with a 16 bit integer time-zone, but
1385 // according to the spec time-zone is "reserved, not supported,
1386 // should be set to 0x000".
1387 pos += 2;
1388 // discard the time zone
1389 return date;
1390 },
1391 /**
1392 * Reads an AMF0 Object from the byte array
1393 * @private
1394 */
1395 readAmf0Object: function(obj) {
1396 var me = this,
1397 key;
1398 obj = obj || {};
1399 // add the object to the objects array so that the AMF0 reference
1400 // type decoder can refer to it by index if needed.
1401 objects.push(obj);
1402 // An AMF0 object consists of a series of string keys and variable-
1403 // type values. The end of the series is marked by an empty string
1404 // followed by the object-end marker (9).
1405 while ((key = me.readAmf0String()) || bytes[pos] !== 9) {
1406 obj[key] = me.readValue();
1407 }
1408 // move the pointer past the object-end marker
1409 pos++;
1410 return obj;
1411 },
1412 /**
1413 * Reads an AMF0 string from the byte array
1414 * @private
1415 */
1416 readAmf0String: function() {
1417 // AMF0 strings begin with a 16 bit byte-length header.
1418 return this.readUtf8(this.readUInt(2));
1419 },
1420 readAmf0Xml: function() {
1421 return this.parseXml(this.readLongString());
1422 },
1423 readAmf3Array: function() {
1424 var me = this,
1425 header = me.readUInt29(),
1426 count, key, array, i;
1427 // AMF considers Arrays in two parts, the dense portion and the
1428 // associative portion. The binary representation of the associative
1429 // portion consists of name/value pairs (potentially none) terminated
1430 // by an empty string. The binary representation of the dense portion
1431 // is the size of the dense portion (potentially zero) followed by an
1432 // ordered list of values (potentially none).
1433 if (header & 1) {
1434 // If the first (low) bit is a 1 read an array instance. The
1435 // remaining 1-28 bits are used to encode the length of the
1436 // dense portion of the array.
1437 count = (header >> 1);
1438 // First read the associative portion of the array (if any). If
1439 // there is an associative portion, the array will be read as a
1440 // javascript object, otherwise it will be a javascript array.
1441 key = me.readAmf3String();
1442 if (key) {
1443 // First key is not an empty string - this is an associative
1444 // array. Read keys and values from the byte array until
1445 // we get to an empty string key
1446 array = {};
1447 objects.push(array);
1448 do {
1449 array[key] = me.readValue();
1450 } while ((key = me.readAmf3String()));
1451 // The dense portion of the array is then read into the
1452 // associative object, keyed by ordinal index.
1453 for (i = 0; i < count; i++) {
1454 array[i] = me.readValue();
1455 }
1456 } else {
1457 // First key is an empty string - this is an array with
1458 // ordinal indices.
1459 array = [];
1460 objects.push(array);
1461 for (i = 0; i < count; i++) {
1462 array.push(me.readValue());
1463 }
1464 }
1465 } else {
1466 // If the first (low) bit is a 0 read an array reference. The
1467 // remaining 1-28 bits are used to encode the reference index
1468 array = objects[header >> 1];
1469 }
1470 return array;
1471 },
1472 /**
1473 * Reads an AMF3 date from the byte array
1474 * @private
1475 */
1476 readAmf3Date: function() {
1477 var me = this,
1478 header = me.readUInt29(),
1479 date;
1480 if (header & 1) {
1481 // If the first (low) bit is a 1, this is a date instance.
1482 date = new Date(me.readDouble());
1483 objects.push(date);
1484 } else {
1485 // If the first (low) bit is a 0, this is a date reference.
1486 // The remaining 1-28 bits encode the reference index
1487 date = objects[header >> 1];
1488 }
1489 return date;
1490 },
1491 /**
1492 * Reads an AMF3 object from the byte array
1493 * @private
1494 */
1495 readAmf3Object: function() {
1496 var me = this,
1497 header = me.readUInt29(),
1498 members = [],
1499 headerLast3Bits, memberCount, className, dynamic, objectTraits, obj, key, klass, i;
1500 // There are 3 different types of object headers, distinguishable
1501 // by the 1-3 least significant bits. All object instances have
1502 // a 1 in the low bit position, while references have a 0:
1503 //
1504 // 0 : object reference
1505 // 011 : traits
1506 // 01 : traits-ref
1507 // 111 : traits-ext
1508 if (header & 1) {
1509 // first (low) bit of 1, denotes an encoded object instance
1510 // The next string is the class name.
1511 headerLast3Bits = (header & 7);
1512 if (headerLast3Bits === 3) {
1513 // If the 3 least significant bits of the header are "011"
1514 // then trait information follows.
1515 className = me.readAmf3String();
1516 // A 1 in the header's 4th least significant byte position
1517 // indicates that dynamic members may follow the sealed
1518 // members.
1519 dynamic = !!(header & 8);
1520 // Shift off the 4 least significant bits, and the remaining
1521 // 1-25 bits encode the number of sealed member names. Read
1522 // as many strings from the byte array as member names.
1523 memberCount = (header >> 4);
1524 for (i = 0; i < memberCount; i++) {
1525 members.push(me.readAmf3String());
1526 }
1527 objectTraits = {
1528 className: className,
1529 dynamic: dynamic,
1530 members: members
1531 };
1532 // An objects traits are cached in the traits array enabling
1533 // the traits for a given class to only be encoded once for
1534 // a series of instances.
1535 traits.push(objectTraits);
1536 } else if ((header & 3) === 1) {
1537 // If the 2 least significant bits are "01", then a traits
1538 // reference follows. The remaining 1-27 bits are used
1539 // to encode the trait reference index.
1540 objectTraits = traits[header >> 2];
1541 className = objectTraits.className;
1542 dynamic = objectTraits.dynamic;
1543 members = objectTraits.members;
1544 memberCount = members.length;
1545 } else if (headerLast3Bits === 7) {}
1546 // if the 3 lease significant bits are "111" then
1547 // externalizable trait data follows
1548 // TODO: implement externalizable traits
1549 if (className) {
1550 klass = Ext.ClassManager.getByAlias('amf.' + className);
1551 obj = klass ? new klass() : {
1552 $className: className
1553 };
1554 } else {
1555 obj = {};
1556 }
1557 objects.push(obj);
1558 // read the sealed member values
1559 for (i = 0; i < memberCount; i++) {
1560 obj[members[i]] = me.readValue();
1561 }
1562 if (dynamic) {
1563 // If the dynamic flag is set, dynamic members may follow
1564 // the sealed members. Read key/value pairs until we
1565 // encounter an empty string key signifying the end of the
1566 // dynamic members.
1567 while ((key = me.readAmf3String())) {
1568 obj[key] = me.readValue();
1569 }
1570 }
1571 // finally, check if we need to convert this class
1572 if ((!klass) && this.converters[className]) {
1573 obj = this.converters[className](obj);
1574 }
1575 } else {
1576 // If the first (low) bit of the header is a 0, this is an
1577 // object reference. The remaining 1-28 significant bits are
1578 // used to encode an object reference index.
1579 obj = objects[header >> 1];
1580 }
1581 return obj;
1582 },
1583 /**
1584 * Reads an AMF3 string from the byte array
1585 * @private
1586 */
1587 readAmf3String: function() {
1588 var me = this,
1589 header = me.readUInt29(),
1590 value;
1591 if (header & 1) {
1592 // If the first (low) bit is a 1, this is a string literal.
1593 // Discard the low bit. The remaining 1-28 bits are used to
1594 // encode the string's byte-length.
1595 value = me.readUtf8(header >> 1);
1596 if (value) {
1597 // the emtpy string is never encoded by reference
1598 strings.push(value);
1599 }
1600 return value;
1601 } else {
1602 // If the first (low) bit is a 0, this is a string reference.
1603 // Discard the low bit, then look up and return the reference
1604 // from the strings array using the remaining 1-28 bits as the
1605 // index.
1606 return strings[header >> 1];
1607 }
1608 },
1609 /**
1610 * Reads an AMF3 XMLDocument type or XML type from the byte array
1611 * @private
1612 */
1613 readAmf3Xml: function() {
1614 var me = this,
1615 header = me.readUInt29(),
1616 doc;
1617 if (header & 1) {
1618 // If the first (low) bit is a 1, this is an xml instance. The
1619 // remaining 1-28 bits encode the byte-length of the xml string.
1620 doc = me.parseXml(me.readUtf8(header >> 1));
1621 objects.push(doc);
1622 } else {
1623 // if the first (low) bit is a 1, this is an xml reference. The
1624 // remaining 1-28 bits encode the reference index.
1625 doc = objects[header >> 1];
1626 }
1627 return doc;
1628 },
1629 /**
1630 * Reads an AMF0 boolean from the byte array
1631 * @private
1632 */
1633 readBoolean: function() {
1634 return !!bytes[pos++];
1635 },
1636 /**
1637 * Reads an AMF3 ByteArray type from the byte array
1638 * @private
1639 */
1640 readByteArray: function() {
1641 var header = this.readUInt29(),
1642 byteArray, end;
1643 if (header & 1) {
1644 // If the first (low) bit is a 1, this is a ByteArray instance.
1645 // The remaining 1-28 bits encode the ByteArray's byte-length.
1646 end = pos + (header >> 1);
1647 // Depending on the browser, "bytes" may be either a Uint8Array
1648 // or an Array. Uint8Arrays don't have Array methods, so
1649 // we have to use Array.prototype.slice to get the byteArray
1650 byteArray = Array.prototype.slice.call(bytes, pos, end);
1651 objects.push(byteArray);
1652 // move the pointer to the first byte after the byteArray that
1653 // was just read
1654 pos = end;
1655 } else {
1656 // if the first (low) bit is a 1, this is a ByteArray reference.
1657 // The remaining 1-28 bits encode the reference index.
1658 byteArray = objects[header >> 1];
1659 }
1660 return byteArray;
1661 },
1662 /**
1663 * Reads a IEEE 754 double-precision binary floating-point number
1664 * @private
1665 */
1666 readDouble: function() {
1667 var byte1 = bytes[pos++],
1668 byte2 = bytes[pos++],
1669 // the first bit of byte1 is the sign (0 = positive, 1 = negative.
1670 // We read this bit by shifting the 7 least significant bits of
1671 // byte1 off to the right.
1672 sign = (byte1 >> 7) ? -1 : 1,
1673 // the exponent takes up the next 11 bits.
1674 exponent = // extract the 7 least significant bits from byte1 and then
1675 // shift them left by 4 bits to make room for the 4 remaining
1676 // bits from byte 2
1677 (((byte1 & 127) << 4) | // add the 4 most significant bits from byte 2 to complete
1678 // the exponent
1679 (byte2 >> 4)),
1680 // the remaining 52 bits make up the significand. read the 4
1681 // least significant bytes of byte 2 to begin the significand
1682 significand = (byte2 & 15),
1683 // The most significant bit of the significand is always 1 for
1684 // a normalized number, therefore it is not stored. This bit is
1685 // referred to as the "hidden bit". The true bit width of the
1686 // significand is 53 if you include the hidden bit. An exponent
1687 // of 0 indicates that this is a subnormal number, and subnormal
1688 // numbers always have a 0 hidden bit.
1689 hiddenBit = exponent ? 1 : 0,
1690 i = 6;
1691 // The operands of all bitwise operators in javascript are converted
1692 // to signed 32 bit integers. It is therefore impossible to construct
1693 // the 52 bit significand by repeatedly shifting its bits and then
1694 // bitwise OR-ing the result with the the next byte. To work around
1695 // this issue, we repeatedly multiply the significand by 2^8 which
1696 // produces the same result as (significand << 8), then we add the
1697 // next byte, which has the same result as a bitwise OR.
1698 while (i--) {
1699 significand = (significand * twoPow8) + bytes[pos++];
1700 }
1701 if (!exponent) {
1702 if (!significand) {
1703 // if both exponent and significand are 0, the number is 0
1704 return 0;
1705 }
1706 // If the exponent is 0, but the significand is not 0, this
1707 // is a subnormal number. Subnormal numbers are encoded with a
1708 // biased exponent of 0, but are interpreted with the value of
1709 // the smallest allowed exponent, which is one greater.
1710 exponent = 1;
1711 }
1712 // 0x7FF (2047) is a special exponent value that represents infinity
1713 // if the significand is 0, and NaN if the significand is not 0
1714 if (exponent === 2047) {
1715 return significand ? NaN : (Infinity * sign);
1716 }
1717 return sign * // The exponent is encoded using an offset binary
1718 // representation with the zero offset being 0x3FF (1023),
1719 // so we have to subtract 0x3FF to get the true exponent
1720 Math.pow(2, exponent - 1023) * // convert the significand to its decimal value by multiplying
1721 // it by 2^52 and then add the hidden bit
1722 (hiddenBit + twoPowN52 * significand);
1723 },
1724 /**
1725 * Reads an AMF0 ECMA Array from the byte array
1726 * @private
1727 */
1728 readEcmaArray: function() {
1729 // An ecma array type is encoded exactly like an anonymous object
1730 // with the exception that it has a 32 bit "count" at the beginning.
1731 // We handle emca arrays by just throwing away the count and then
1732 // letting the object decoder handle the rest.
1733 pos += 4;
1734 return this.readAmf0Object();
1735 },
1736 /**
1737 * Returns false. Used for reading the false type
1738 * @private
1739 */
1740 readFalse: function() {
1741 return false;
1742 },
1743 /**
1744 * Reads a long string (longer than 65535 bytes) from the byte array
1745 * @private
1746 */
1747 readLongString: function() {
1748 // long strings begin with a 32 bit byte-length header.
1749 return this.readUtf8(this.readUInt(4));
1750 },
1751 /**
1752 * Returns null. Used for reading the null type
1753 * @private
1754 */
1755 readNull: function() {
1756 return null;
1757 },
1758 /**
1759 * Reads a reference from the byte array. Reference types are used to
1760 * avoid duplicating data if the same instance of a complex object (which
1761 * is defined in AMF0 as an anonymous object, typed object, array, or
1762 * ecma-array) is included in the data more than once.
1763 * @private
1764 */
1765 readReference: function() {
1766 // a reference type contains a single 16 bit integer that represents
1767 // the index of an already deserialized object in the objects array
1768 return objects[this.readUInt(2)];
1769 },
1770 /**
1771 * Reads an AMF0 strict array (an array with ordinal indices)
1772 * @private
1773 */
1774 readStrictArray: function() {
1775 var me = this,
1776 len = me.readUInt(4),
1777 arr = [];
1778 objects.push(arr);
1779 while (len--) {
1780 arr.push(me.readValue());
1781 }
1782 return arr;
1783 },
1784 /**
1785 * Returns true. Used for reading the true type
1786 * @private
1787 */
1788 readTrue: Ext.returnTrue,
1789 /**
1790 * Reads an AMF0 typed object from the byte array
1791 * @private
1792 */
1793 readTypedObject: function() {
1794 var me = this,
1795 className = me.readAmf0String(),
1796 klass, instance, modified;
1797 klass = Ext.ClassManager.getByAlias('amf.' + className);
1798 instance = klass ? new klass() : {
1799 $className: className
1800 };
1801 // if there is no klass, mark the classname for easier parsing of returned results
1802 modified = me.readAmf0Object(instance);
1803 // check if we need to convert this class
1804 if ((!klass) && this.converters[className]) {
1805 modified = this.converters[className](instance);
1806 }
1807 return modified;
1808 },
1809 /**
1810 * Reads an unsigned integer from the byte array
1811 * @private
1812 * @param {Number} byteCount the number of bytes to read, e.g. 2 to read
1813 * a 16 bit integer, 4 to read a 32 bit integer, etc.
1814 * @return {Number}
1815 */
1816 readUInt: function(byteCount) {
1817 var i = 1,
1818 result;
1819 // read the first byte
1820 result = bytes[pos++];
1821 // if this is a multi-byte int, loop over the remaining bytes
1822 for (; i < byteCount; ++i) {
1823 // shift the result 8 bits to the left and add the next byte.
1824 result = (result << 8) | bytes[pos++];
1825 }
1826 return result;
1827 },
1828 /**
1829 * Reads an unsigned 29-bit integer from the byte array.
1830 * AMF 3 makes use of a special compact format for writing integers to
1831 * reduce the number of bytes required for encoding. As with a normal
1832 * 32-bit integer, up to 4 bytes are required to hold the value however
1833 * the high bit of the first 3 bytes are used as flags to determine
1834 * whether the next byte is part of the integer. With up to 3 bits of
1835 * the 32 bits being used as flags, only 29 significant bits remain for
1836 * encoding an integer. This means the largest unsigned integer value
1837 * that can be represented is 2^29-1.
1838 *
1839 * (hex) : (binary)
1840 * 0x00000000 - 0x0000007F : 0xxxxxxx
1841 * 0x00000080 - 0x00003FFF : 1xxxxxxx 0xxxxxxx
1842 * 0x00004000 - 0x001FFFFF : 1xxxxxxx 1xxxxxxx 0xxxxxxx
1843 * 0x00200000 - 0x3FFFFFFF : 1xxxxxxx 1xxxxxxx 1xxxxxxx xxxxxxxx
1844 * @private
1845 * @return {Number}
1846 */
1847 readUInt29: function() {
1848 var value = bytes[pos++],
1849 nextByte;
1850 if (value & 128) {
1851 // if the high order bit of the first byte is a 1, the next byte
1852 // is also part of this integer.
1853 nextByte = bytes[pos++];
1854 // remove the high order bit from both bytes before combining them
1855 value = ((value & 127) << 7) | (nextByte & 127);
1856 if (nextByte & 128) {
1857 // if the high order byte of the 2nd byte is a 1, then
1858 // there is a 3rd byte
1859 nextByte = bytes[pos++];
1860 // remove the high order bit from the 3rd byte before
1861 // adding it to the value
1862 value = (value << 7) | (nextByte & 127);
1863 if (nextByte & 128) {
1864 // 4th byte is also part of the integer
1865 nextByte = bytes[pos++];
1866 // use all 8 bits of the 4th byte
1867 value = (value << 8) | nextByte;
1868 }
1869 }
1870 }
1871 return value;
1872 },
1873 /**
1874 * Returns undefined. Used for reading the undefined type
1875 * @private
1876 */
1877 readUndefined: Ext.emptyFn,
1878 /**
1879 * Returns undefined. Used for reading the unsupported type
1880 * @private
1881 */
1882 readUnsupported: Ext.emptyFn,
1883 /**
1884 * Reads a UTF-8 string from the byte array
1885 * @private
1886 * @param {Number} byteLength The number of bytes to read
1887 * @return {String}
1888 */
1889 readUtf8: function(byteLength) {
1890 var end = pos + byteLength,
1891 // the string's end position
1892 chars = [],
1893 charCount = 0,
1894 maxCharCount = 65535,
1895 charArrayCount = 1,
1896 result = [],
1897 i = 0,
1898 charArrays, byteCount, charCode;
1899 charArrays = [
1900 chars
1901 ];
1902 // UTF-8 characters may be encoded using 1-4 bytes. The number of
1903 // bytes that a character consumes is determined by reading the
1904 // leading byte. Values 0-127 in the leading byte indicate a single-
1905 // byte ASCII-compatible character. Values 192-223 (bytes with "110"
1906 // in the high-order position) indicate a 2-byte character, values
1907 // 224-239 (bytes with "1110" in the high-order position) indicate a
1908 // 3-byte character, and values 240-247 (bytes with "11110" in the
1909 // high-order position) indicate a 4-byte character. The remaining
1910 // bits of the leading byte hold bits of the encoded character, with
1911 // leading zeros if necessary.
1912 //
1913 // The continuation bytes all have "10" in the high-order position,
1914 // which means only the 6 least significant bits of continuation
1915 // bytes are available to hold the bits of the encoded character.
1916 //
1917 // The following table illustrates the binary format of UTF-8
1918 // characters:
1919 //
1920 // Bits Byte 1 Byte 2 Byte 3 Byte 4
1921 // -----------------------------------------------------
1922 // 7 0xxxxxxx
1923 // 11 110xxxxx 10xxxxxx
1924 // 16 1110xxxx 10xxxxxx 10xxxxxx
1925 // 21 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
1926 while (pos < end) {
1927 // read a byte from the byte array - if the byte's value is less
1928 // than 128 we are dealing with a single byte character
1929 charCode = bytes[pos++];
1930 if (charCode > 127) {
1931 // if the byte's value is greater than 127 we are dealing
1932 // with a multi-byte character.
1933 if (charCode > 239) {
1934 // a leading-byte value greater than 239 means this is a
1935 // 4-byte character
1936 byteCount = 4;
1937 // Use only the 3 least-significant bits of the leading
1938 // byte of a 4-byte character. This is achieved by
1939 // applying the following bit mask:
1940 // (charCode & 0x07)
1941 // which is equivalent to:
1942 // 11110xxx (the byte)
1943 // AND 00000111 (the mask)
1944 charCode = (charCode & 7);
1945 } else if (charCode > 223) {
1946 // a leading-byte value greater than 223 but less than
1947 // 240 means this is a 3-byte character
1948 byteCount = 3;
1949 // Use only the 4 least-significant bits of the leading
1950 // byte of a 3-byte character. This is achieved by
1951 // applying the following bit mask:
1952 // (charCode & 0x0F)
1953 // which is equivalent to:
1954 // 1110xxxx (the byte)
1955 // AND 00001111 (the mask)
1956 charCode = (charCode & 15);
1957 } else {
1958 // a leading-byte value less than 224 but (implicitly)
1959 // greater than 191 means this is a 2-byte character
1960 byteCount = 2;
1961 // Use only the 5 least-significant bits of the first
1962 // byte of a 2-byte character. This is achieved by
1963 // applying the following bit mask:
1964 // (charCode & 0x1F)
1965 // which is equivalent to:
1966 // 110xxxxx (the byte)
1967 // AND 00011111 (the mask)
1968 charCode = (charCode & 31);
1969 }
1970 while (--byteCount) {
1971 // get one continuation byte. then strip off the leading
1972 // "10" by applying the following bit mask:
1973 // (b & 0x3F)
1974 // which is equialent to:
1975 // 10xxxxxx (the byte)
1976 // AND 00111111 (the mask)
1977 // That leaves 6 remaining bits on the continuation byte
1978 // which are concatenated onto the character's bits
1979 charCode = ((charCode << 6) | (bytes[pos++] & 63));
1980 }
1981 }
1982 chars.push(charCode);
1983 if (++charCount === maxCharCount) {
1984 charArrays.push(chars = []);
1985 charCount = 0;
1986 charArrayCount++;
1987 }
1988 }
1989 // At this point we end up with an array of char arrays, each char
1990 // array being no longer than 65,535 characters, the fastest way to
1991 // turn these char arrays into strings is to pass them as the
1992 // arguments to fromCharCode (fortunately all currently supported
1993 // browsers can handle at least 65,535 function arguments).
1994 for (; i < charArrayCount; i++) {
1995 // create a result array containing the strings converted from
1996 // the individual character arrays.
1997 result.push(String.fromCharCode.apply(String, charArrays[i]));
1998 }
1999 return result.join('');
2000 },
2001 /**
2002 * Reads an AMF "value-type" from the byte array. Automatically detects
2003 * the data type by reading the "type marker" from the first byte after
2004 * the pointer.
2005 * @private
2006 */
2007 readValue: function() {
2008 var me = this,
2009 marker = bytes[pos++];
2010 // With the introduction of AMF3, a special type marker was added to
2011 // AMF0 to signal a switch to AMF3 serialization. This allows a packet
2012 // to start out in AMF 0 and switch to AMF 3 on the first complex type
2013 // to take advantage of the more the efficient encoding of AMF 3.
2014 if (marker === 17) {
2015 // change the version to AMF3 when we see a 17 marker
2016 me.version = 3;
2017 marker = bytes[pos++];
2018 }
2019 return me[me.typeMap[me.version][marker]]();
2020 },
2021 /**
2022 * Converters used in converting specific typed Flex classes to JavaScript usable form.
2023 * @private
2024 */
2025 converters: {
2026 'flex.messaging.io.ArrayCollection': function(obj) {
2027 return obj.source || [];
2028 }
2029 }
2030 };
2031 });
2032 // array collections have a source var that contains the actual data
2033
2034 // @tag enterprise
2035 /**
2036 * The AMF Reader is used by an {@link Ext.data.amf.Proxy AMF Proxy} to read
2037 * records from a server response that contains binary data in either AMF0 or
2038 * AMF3 format. AMF Reader constructs an {@link Ext.data.amf.Packet AMF Packet}
2039 * and uses it to decode the binary data into javascript objects, then simply
2040 * allows its superclass ({@link Ext.data.reader.Json}) to handle converting the
2041 * raw javascript objects into {@link Ext.data.Model} instances.
2042 *
2043 * For a more detailed tutorial see the [AMF Guide](#/guide/amf).
2044 */
2045 Ext.define('Ext.data.amf.Reader', {
2046 extend: 'Ext.data.reader.Json',
2047 alias: 'reader.amf',
2048 requires: [
2049 'Ext.data.amf.Packet'
2050 ],
2051 /**
2052 * @cfg {Number} messageIndex
2053 * AMF Packets can contain multiple messages. This config specifies the
2054 * 0-based index of the message that contains the record data.
2055 */
2056 messageIndex: 0,
2057 /**
2058 * Reads records from a XMLHttpRequest response object containing a binary
2059 * AMF Packet and returns a ResultSet.
2060 * @param {Object} response The XMLHttpRequest response object
2061 * @return {Ext.data.ResultSet}
2062 */
2063 read: function(response) {
2064 var me = this,
2065 bytes = response.responseBytes,
2066 packet, messages, resultSet;
2067 if (!bytes) {
2068 throw "AMF Reader cannot process the response because it does not contain binary data. Make sure the Proxy's 'binary' config is true.";
2069 }
2070 packet = new Ext.data.amf.Packet();
2071 packet.decode(bytes);
2072 messages = packet.messages;
2073 if (messages.length) {
2074 resultSet = me.readRecords(messages[me.messageIndex].body);
2075 } else {
2076 // no messages, return null result set
2077 resultSet = me.nullResultSet;
2078 if (packet.invalid) {
2079 // packet contains invalid data
2080 resultSet.success = false;
2081 }
2082 }
2083 return resultSet;
2084 }
2085 });
2086
2087 // @tag enterprise
2088 /**
2089 * The AMF Proxy is an {@link Ext.data.proxy.Ajax Ajax Proxy} that requests
2090 * binary data from a remote server and parses it into records using an
2091 * {@link Ext.data.amf.Reader AMF Reader} for use in a
2092 * {@link Ext.data.Store Store}.
2093 *
2094 * Ext.create('Ext.data.Store', {
2095 * model: 'Foo',
2096 * proxy: {
2097 * type: 'amf',
2098 * url: 'some/url'
2099 * }
2100 * });
2101 *
2102 * For a detailed tutorial on using AMF data see the [AMF Guide](#/guide/amf).
2103 *
2104 * **Note: ** _This functionality is only available with the purchase of
2105 * Sencha Complete. For more information about using this class, please visit
2106 * our [Sencha Complete](https://www.sencha.com/products/complete/) product page._
2107 *
2108 */
2109 Ext.define('Ext.data.amf.Proxy', {
2110 extend: 'Ext.data.proxy.Ajax',
2111 alias: 'proxy.amf',
2112 requires: [
2113 'Ext.data.amf.Reader'
2114 ],
2115 /**
2116 * @cfg
2117 * @inheritdoc
2118 */
2119 binary: true,
2120 /**
2121 * @cfg
2122 * @inheritdoc
2123 */
2124 reader: 'amf'
2125 });
2126
2127 // @tag enterprise
2128 /**
2129 * @class Ext.data.amf.RemotingMessage
2130 * Represents a remote call to be sent to the server.
2131 */
2132 Ext.define('Ext.data.amf.RemotingMessage', {
2133 alias: 'data.amf.remotingmessage',
2134 config: {
2135 $flexType: 'flex.messaging.messages.RemotingMessage',
2136 /**
2137 * @property {Array} body - typically an array of parameters to pass to a method call
2138 */
2139 body: [],
2140 /**
2141 * @property {String} clientID - identifies the calling client.
2142 */
2143 clientId: "",
2144 /**
2145 * @property {String} destination - the service destination on the server
2146 */
2147 destination: "",
2148 /**
2149 * @property {Object} headers - the headers to attach to the message.
2150 * Would typically contain the DSEndpoint and DSId fields.
2151 */
2152 headers: [],
2153 /**
2154 * @property {String} messageId - message identifier
2155 */
2156 messageId: "",
2157 /**
2158 * @property {String} operation - the method name to call
2159 */
2160 operation: "",
2161 /**
2162 * @property {Array} source - should be empty for security purposes
2163 */
2164 source: "",
2165 /**
2166 * @property {Number} timestamp - when the message was created
2167 */
2168 timestamp: [],
2169 /**
2170 * @property {Number} timeToLive - how long the message is still valid for passing
2171 */
2172 timeToLive: []
2173 },
2174 /**
2175 * Creates new message.
2176 * @param {Object} config Configuration options
2177 */
2178 constructor: function(config) {
2179 this.initConfig(config);
2180 },
2181 /**
2182 * Returns an AMFX encoded version of the message.
2183 */
2184 encodeMessage: function() {
2185 var encoder = Ext.create('Ext.data.amf.XmlEncoder'),
2186 cleanObj;
2187 cleanObj = Ext.copyTo({}, this, "$flexType,body,clientId,destination,headers,messageId,operation,source,timestamp,timeToLive", true);
2188 encoder.writeObject(cleanObj);
2189 return encoder.body;
2190 }
2191 });
2192
2193 // @tag enterprise
2194 /**
2195 * @class Ext.data.amf.XmlDecoder
2196 * This class parses an XML-based AMFX message and returns the deserialized
2197 * objects. You should not need to use this class directly. It's mostly used by
2198 * the AMFX Direct implementation.
2199 * To decode a message, first construct a Decoder:
2200 *
2201 * decoder = Ext.create('Ext.data.amf.XmlDecoder');
2202 *
2203 * Then ask it to read in the message :
2204 *
2205 * resp = decoder.readAmfxMessage(str);
2206 *
2207 * For more information on working with AMF data please refer to the
2208 * [AMF Guide](#/guide/amf).
2209 */
2210 Ext.define('Ext.data.amf.XmlDecoder', {
2211 alias: 'data.amf.xmldecoder',
2212 statics: {
2213 /**
2214 * Parses an xml string and returns an xml document
2215 * @private
2216 * @param {String} xml
2217 */
2218 readXml: function(xml) {
2219 var doc;
2220 if (window.DOMParser) {
2221 doc = (new DOMParser()).parseFromString(xml, "text/xml");
2222 } else {
2223 doc = new ActiveXObject("Microsoft.XMLDOM");
2224 doc.loadXML(xml);
2225 }
2226 return doc;
2227 },
2228 /**
2229 * parses a node containing a byte array in hexadecimal format, returning the reconstructed array.
2230 * @param {HTMLElement/XMLElement} node the node
2231 * @return {Array} a byte array
2232 */
2233 readByteArray: function(node) {
2234 var bytes = [],
2235 c, i, str;
2236 str = node.firstChild.nodeValue;
2237 for (i = 0; i < str.length; i = i + 2) {
2238 c = str.substr(i, 2);
2239 bytes.push(parseInt(c, 16));
2240 }
2241 return bytes;
2242 },
2243 /**
2244 * Deserializes an AMF3 binary object from a byte array
2245 * @param {Array} bytes the byte array containing one AMF3-encoded value
2246 * @return {Object} the decoded value
2247 */
2248 readAMF3Value: function(bytes) {
2249 var packet;
2250 packet = Ext.create('Ext.data.amf.Packet');
2251 return packet.decodeValue(bytes);
2252 },
2253 /**
2254 * Accepts Flex-style UID and decodes the number in the first four bytes (8 hex digits) of data.
2255 * @param {String} messageId the message ID
2256 * @return {Number} the transaction ID
2257 */
2258 decodeTidFromFlexUID: function(messageId) {
2259 var str;
2260 str = messageId.substr(0, 8);
2261 return parseInt(str, 16);
2262 }
2263 },
2264 /**
2265 * Creates new encoder.
2266 * @param {Object} config Configuration options
2267 */
2268 constructor: function(config) {
2269 this.initConfig(config);
2270 this.clear();
2271 },
2272 /**
2273 * Clears the accumulated data and reference tables
2274 */
2275 clear: function() {
2276 // reset reference counters
2277 this.objectReferences = [];
2278 this.traitsReferences = [];
2279 this.stringReferences = [];
2280 },
2281 /**
2282 * Reads and returns a decoded AMFX packet.
2283 * @param {String} xml the xml of the message
2284 * @return {Object} the response object containing the message
2285 */
2286 readAmfxMessage: function(xml) {
2287 var doc, amfx, body, i,
2288 resp = {};
2289 this.clear();
2290 // reset counters
2291 doc = Ext.data.amf.XmlDecoder.readXml(xml);
2292 amfx = doc.getElementsByTagName('amfx')[0];
2293 //<debug>
2294 if (!amfx) {
2295 Ext.warn.log("No AMFX tag in message");
2296 }
2297 if (amfx.getAttribute('ver') != "3") {
2298 Ext.raise("Unsupported AMFX version: " + amfx.getAttribute('ver'));
2299 }
2300 //</debug>
2301 body = amfx.getElementsByTagName('body')[0];
2302 resp.targetURI = body.getAttribute('targetURI');
2303 resp.responseURI = body.getAttribute('responseURI');
2304 // most likely empty string
2305 for (i = 0; i < body.childNodes.length; i++) {
2306 if (body.childNodes.item(i).nodeType != 1) {
2307 // only process element nodes, ignore white space and text nodes
2308
2309 continue;
2310 }
2311 resp.message = this.readValue(body.childNodes.item(i));
2312 break;
2313 }
2314 // no need to keep iterating
2315 return resp;
2316 },
2317 /**
2318 * Parses an HTML element returning the appropriate JavaScript value from the AMFX data.
2319 * @param {HTMLElement} node The node to parse
2320 * @return {Object} a JavaScript object or value
2321 */
2322 readValue: function(node) {
2323 var val;
2324 if (typeof node.normalize === 'function') {
2325 node.normalize();
2326 }
2327 // 2DO: handle references!
2328 if (node.tagName == "null") {
2329 return null;
2330 } else if (node.tagName == "true") {
2331 return true;
2332 } else if (node.tagName == "false") {
2333 return false;
2334 } else if (node.tagName == "string") {
2335 return this.readString(node);
2336 } else if (node.tagName == "int") {
2337 return parseInt(node.firstChild.nodeValue);
2338 } else if (node.tagName == "double") {
2339 return parseFloat(node.firstChild.nodeValue);
2340 } else if (node.tagName == "date") {
2341 val = new Date(parseFloat(node.firstChild.nodeValue));
2342 // record in object reference table
2343 this.objectReferences.push(val);
2344 return val;
2345 } else if (node.tagName == "dictionary") {
2346 return this.readDictionary(node);
2347 } else if (node.tagName == "array") {
2348 return this.readArray(node);
2349 } else if (node.tagName == "ref") {
2350 return this.readObjectRef(node);
2351 } else if (node.tagName == "object") {
2352 return this.readObject(node);
2353 } else if (node.tagName == "xml") {
2354 // the CDATA content of the node is a parseable XML document. parse it.
2355 return Ext.data.amf.XmlDecoder.readXml(node.firstChild.nodeValue);
2356 } else if (node.tagName == "bytearray") {
2357 // a byte array is usually an AMF stream. Parse it to a byte array, then pass through the AMF decoder to get the objects inside
2358 return Ext.data.amf.XmlDecoder.readAMF3Value(Ext.data.amf.XmlDecoder.readByteArray(node));
2359 }
2360 //<debug>
2361 Ext.raise("Unknown tag type: " + node.tagName);
2362 //</debug>
2363 return null;
2364 },
2365 /**
2366 * Reads a string or string reference and return the value
2367 * @param {HTMLElement/XMLElement} node the node containing a string object
2368 * @return {String} the parsed string
2369 */
2370 readString: function(node) {
2371 var val;
2372 if (node.getAttributeNode('id')) {
2373 return this.stringReferences[parseInt(node.getAttribute('id'))];
2374 }
2375 val = (node.firstChild ? node.firstChild.nodeValue : "") || "";
2376 this.stringReferences.push(val);
2377 return val;
2378 },
2379 /**
2380 * Parses and returns an ordered list of trait names
2381 * @param {HTMLElement/XMLElement} node the traits node from the XML doc
2382 * @return {Array} an array of ordered trait names or null if it's an externalizable object
2383 */
2384 readTraits: function(node) {
2385 var traits = [],
2386 i, rawtraits;
2387 if (node === null) {
2388 return null;
2389 }
2390 if (node.getAttribute('externalizable') == "true") {
2391 // no traits since it's an externalizable or a null object.
2392 return null;
2393 }
2394 if (node.getAttributeNode('id')) {
2395 // return traits reference
2396 return this.traitsReferences[parseInt(node.getAttributeNode('id').value)];
2397 }
2398 /* // empty anonymous objects still seem to get their empty traits in the reference table
2399 if (!node.hasChildNodes()) {
2400 var className = node.parentNode.getElementsByTagName('type');
2401 if (className.length == 0) {
2402 return traits; // special case of an anonymous object with no traits. Does not get reference counted
2403 }
2404 }
2405 */
2406 rawtraits = node.childNodes;
2407 for (i = 0; i < rawtraits.length; i++) {
2408 if (rawtraits.item(i).nodeType != 1) {
2409 // only process element nodes, ignore white space and text nodes
2410
2411 continue;
2412 }
2413 // this will be a string, but let the readValue function handle it nonetheless
2414 traits.push(this.readValue(rawtraits.item(i)));
2415 }
2416 // register traits in ref table:
2417 this.traitsReferences.push(traits);
2418 return traits;
2419 },
2420 /**
2421 * Parses and return an object / array / dictionary / date from reference
2422 * @param {HTMLElement/XMLElement} node the ref node
2423 * @return {Object} the previously instantiated object referred to by the ref node
2424 */
2425 readObjectRef: function(node) {
2426 var id;
2427 id = parseInt(node.getAttribute('id'));
2428 return this.objectReferences[id];
2429 },
2430 /**
2431 * Parses and returns an AMFX object.
2432 * @param {HTMLElement/XMLElement} node the `<object>` node to parse
2433 * @return {Object} the deserialized object
2434 */
2435 readObject: function(node) {
2436 var obj,
2437 traits = [],
2438 traitsNode, i, j, n, key, val,
2439 klass = null,
2440 className;
2441 className = node.getAttribute('type');
2442 if (className) {
2443 klass = Ext.ClassManager.getByAlias('amfx.' + className);
2444 }
2445 // check if special case for class
2446 obj = klass ? new klass() : (className ? {
2447 $className: className
2448 } : {});
2449 // if there is no klass, mark the classname for easier parsing of returned results
2450 // check if we need special handling for this class
2451 if ((!klass) && this.converters[className]) {
2452 obj = this.converters[className](this, node);
2453 return obj;
2454 }
2455 // we're done
2456 traitsNode = node.getElementsByTagName('traits')[0];
2457 traits = this.readTraits(traitsNode);
2458 //<debug>
2459 if (traits === null) {
2460 Ext.raise("No support for externalizable object: " + className);
2461 }
2462 //</debug>
2463 // Register object if ref table, in case there's a cyclical reference coming
2464 this.objectReferences.push(obj);
2465 // Now we expect an item for each trait name we have. We assume it's an ordered list. We'll skip the first (traits) tag
2466 j = 0;
2467 for (i = 0; i < node.childNodes.length; i++) {
2468 n = node.childNodes.item(i);
2469 if (n.nodeType != 1) {
2470 // Ignore text nodes and non-element nodes
2471
2472 continue;
2473 }
2474 if (n.tagName == "traits") {
2475 // ignore the traits node. We've already covered it.
2476
2477 continue;
2478 }
2479 key = traits[j];
2480 val = this.readValue(n);
2481 j = j + 1;
2482 obj[key] = val;
2483 //<debug>
2484 if (j > traits.length) {
2485 Ext.raise("Too many items for object, not enough traits: " + className);
2486 }
2487 }
2488 //</debug>
2489 return obj;
2490 },
2491 /**
2492 * Parses and returns an AMFX array.
2493 * @param {HTMLElement/XMLElement} node the array node
2494 * @return {Array} the deserialized array
2495 */
2496 readArray: function(node) {
2497 var arr = [],
2498 n, i, j, l, name, val, len, childnodes, cn;
2499 // register array in object references table before we parse, in case of circular references
2500 this.objectReferences.push(arr);
2501 len = parseInt(node.getAttributeNode('length').value);
2502 i = 0;
2503 // the length only accounts for the ordinal values. For the rest, we'll read them as ECMA key-value pairs
2504 for (l = 0; l < node.childNodes.length; l++) {
2505 n = node.childNodes.item(l);
2506 if (n.nodeType != 1) {
2507 // Ignore text nodes and non-element nodes
2508
2509 continue;
2510 }
2511 if (n.tagName == "item") {
2512 // parse item node
2513 name = n.getAttributeNode('name').value;
2514 childnodes = n.childNodes;
2515 for (j = 0; j < childnodes.length; j++) {
2516 cn = childnodes.item(j);
2517 if (cn.nodeType != 1) {
2518 // Ignore text nodes and non-element nodes
2519
2520 continue;
2521 }
2522 val = this.readValue(cn);
2523 break;
2524 }
2525 // out of loop. We've found our value
2526 arr[name] = val;
2527 } else {
2528 // ordinal node
2529 arr[i] = this.readValue(n);
2530 i++;
2531 //<debug>
2532 if (i > len) {
2533 Ext.raise("Array has more items than declared length: " + i + " > " + len);
2534 }
2535 }
2536 }
2537 //</debug>
2538 //<debug>
2539 if (i < len) {
2540 Ext.raise("Array has less items than declared length: " + i + " < " + len);
2541 }
2542 //</debug>
2543 return arr;
2544 },
2545 /**
2546 * Parses and returns an AMFX dictionary.
2547 * @param {HTMLElement/XMLElement} node the `<dictionary>` node
2548 * @return {Object} a javascript object with the dictionary value-pair elements
2549 */
2550 readDictionary: function(node) {
2551 // For now, handle regular objects
2552 var dict = {},
2553 key, val, i, j, n, len;
2554 len = parseInt(node.getAttribute('length'));
2555 // Register dictionary in the ref table, in case there's a cyclical reference coming
2556 this.objectReferences.push(dict);
2557 // now find pairs of keys and values
2558 key = null;
2559 val = null;
2560 j = 0;
2561 for (i = 0; i < node.childNodes.length; i++) {
2562 n = node.childNodes.item(i);
2563 if (n.nodeType != 1) {
2564 // Ignore text nodes and non-element nodes
2565
2566 continue;
2567 }
2568 if (!key) {
2569 key = this.readValue(n);
2570
2571 continue;
2572 }
2573 // next element is the value
2574 val = this.readValue(n);
2575 j = j + 1;
2576 dict[key] = val;
2577 key = null;
2578 val = null;
2579 }
2580 //<debug>
2581 if (j != len) {
2582 Ext.raise("Incorrect number of dictionary values: " + j + " != " + len);
2583 }
2584 //</debug>
2585 return dict;
2586 },
2587 /**
2588 * Converts externalizable flex objects with a source array to a regular array.
2589 * @private
2590 */
2591 convertObjectWithSourceField: function(node) {
2592 var i, n, val;
2593 for (i = 0; i < node.childNodes.length; i++) {
2594 n = node.childNodes.item(i);
2595 if (n.tagName == "bytearray") {
2596 val = this.readValue(n);
2597 this.objectReferences.push(val);
2598 return val;
2599 }
2600 }
2601 return null;
2602 },
2603 // we shouldn't reach here, but just in case
2604 /**
2605 * Converters used in converting specific typed Flex classes to JavaScript usable form.
2606 * @private
2607 */
2608 converters: {
2609 'flex.messaging.io.ArrayCollection': function(decoder, node) {
2610 return decoder.convertObjectWithSourceField(node);
2611 },
2612 'mx.collections.ArrayList': function(decoder, node) {
2613 return decoder.convertObjectWithSourceField(node);
2614 },
2615 'mx.collections.ArrayCollection': function(decoder, node) {
2616 return decoder.convertObjectWithSourceField(node);
2617 }
2618 }
2619 });
2620
2621 // @tag enterprise
2622 /**
2623 * @class Ext.data.amf.XmlEncoder
2624 * This class serializes data in the Action Message Format XML (AMFX) format.
2625 * It can write simple and complex objects, to be used in conjunction with an
2626 * AMFX-compliant server.
2627 * To create an encoded XMl, first construct an Encoder:
2628 *
2629 * var encoder = Ext.create('Ext.data.amf.XmlEncoder');
2630 *
2631 * Then use the writer methods to out data to the :
2632 *
2633 * encoder.writeObject(1);
2634 * encoder.writeObject({a: "b"});
2635 *
2636 * And access the data through the #bytes property:
2637 * encoder.body;
2638 *
2639 * You can also reset the class to start a new body:
2640 *
2641 * encoder.clear();
2642 *
2643 * Current limitations:
2644 * AMF3 format (format:3)
2645 * - Each object is written out explicitly, not using the reference tables
2646 * supported by the AMFX format. This means the function does NOT support
2647 * circular reference objects.
2648 * - Objects that aren't Arrays, Dates, Strings, Document (XML) or primitive
2649 * values will be written out as anonymous objects with dynamic data.
2650 * - If the object has a $flexType field, that field will be used in signifying
2651 * the object-type as an attribute, instead of being passed as data.
2652 * - There's no JavaScript equivalent to the ByteArray type in ActionScript,
2653 * hence data will never be searialized as ByteArrays by the writeObject
2654 * function. A writeByteArray method is provided for writing out ByteArray objects.
2655 *
2656 * For more information on working with AMF data please refer to the
2657 * [AMF Guide](#/guide/amf).
2658 */
2659 Ext.define('Ext.data.amf.XmlEncoder', {
2660 alias: 'data.amf.xmlencoder',
2661 /**
2662 * @property {String} body - The output string
2663 */
2664 body: "",
2665 statics: {
2666 /**
2667 * Utility function to generate a flex-friendly UID
2668 * @param {Number} id used in the first 8 chars of the id. If not provided, a random number will be used.
2669 * @return {String} a string-encoded opaque UID
2670 */
2671 generateFlexUID: function(id) {
2672 var uid = "",
2673 i, j, t;
2674 if (id === undefined) {
2675 id = Ext.Number.randomInt(0, 4.294967295E9);
2676 }
2677 // The format of a UID is XXXXXXXX-XXXX-XXXX-XXXX-YYYYYYYYXXXX
2678 // where each X is a random hex digit and each Y is a hex digit from the least significant part of a time stamp.
2679 t = (id + 4.294967296E9).toString(16).toUpperCase();
2680 // padded
2681 uid = t.substr(t.length - 8, 8);
2682 // last 8 chars
2683 for (j = 0; j < 3; j++) {
2684 // 3 -XXXX segments
2685 uid += "-";
2686 for (i = 0; i < 4; i++) {
2687 uid += Ext.Number.randomInt(0, 15).toString(16).toUpperCase();
2688 }
2689 }
2690 uid += "-";
2691 // add timestamp
2692 t = new Number(new Date()).valueOf().toString(16).toUpperCase();
2693 // get the String representation of milliseconds in hex format
2694 j = 0;
2695 if (t.length < 8) {
2696 // pad with "0" if needed
2697 for (i = 0; i < t.length - 8; i++) {
2698 j++;
2699 uid += "0";
2700 }
2701 }
2702 // actual timestamp:
2703 uid += t.substr(-(8 - j));
2704 // last few chars
2705 // and last 4 random digits
2706 for (i = 0; i < 4; i++) {
2707 uid += Ext.Number.randomInt(0, 15).toString(16).toUpperCase();
2708 }
2709 return uid;
2710 }
2711 },
2712 /**
2713 * Creates new encoder.
2714 * @param {Object} config Configuration options
2715 */
2716 constructor: function(config) {
2717 this.initConfig(config);
2718 this.clear();
2719 },
2720 /**
2721 * Clears the accumulated data, starting with an empty string
2722 */
2723 clear: function() {
2724 this.body = "";
2725 },
2726 /**
2727 * Returns the encoding for undefined (which is the same as the encoding for null)
2728 */
2729 encodeUndefined: function() {
2730 return this.encodeNull();
2731 },
2732 /**
2733 * Writes the undefined value to the string
2734 */
2735 writeUndefined: function() {
2736 this.write(this.encodeUndefined());
2737 },
2738 /**
2739 * Returns the encoding for null
2740 */
2741 encodeNull: function() {
2742 return "<null />";
2743 },
2744 /**
2745 * Writes the null value to the string
2746 */
2747 writeNull: function() {
2748 this.write(this.encodeNull());
2749 },
2750 /**
2751 * Returns an encoded boolean
2752 * @param {Boolean} val a boolean value
2753 */
2754 encodeBoolean: function(val) {
2755 var str;
2756 if (val) {
2757 str = "<true />";
2758 } else {
2759 str = "<false />";
2760 }
2761 return str;
2762 },
2763 /**
2764 * Writes a boolean value to the string
2765 * @param {Boolean} val a boolean value
2766 */
2767 writeBoolean: function(val) {
2768 this.write(this.encodeBoolean(val));
2769 },
2770 /**
2771 * Returns an encoded string
2772 * @param {String} str the string to encode
2773 */
2774 encodeString: function(str) {
2775 var ret;
2776 if (str === "") {
2777 ret = "<string />";
2778 } else {
2779 ret = "<string>" + str + "</string>";
2780 }
2781 return ret;
2782 },
2783 /**
2784 * Writes a string tag with the string content.
2785 * @param {String} str the string to encode
2786 */
2787 writeString: function(str) {
2788 this.write(this.encodeString(str));
2789 },
2790 /**
2791 * Returns an encoded int
2792 * @param {Number} num the integer to encode
2793 */
2794 encodeInt: function(num) {
2795 return "<int>" + num.toString() + "</int>";
2796 },
2797 /**
2798 * Writes a int tag with the content.
2799 * @param {Number} num the integer to encode
2800 */
2801 writeInt: function(num) {
2802 this.write(this.encodeInt(num));
2803 },
2804 /**
2805 * Returns an encoded double
2806 * @param {Number} num the double to encode
2807 */
2808 encodeDouble: function(num) {
2809 return "<double>" + num.toString() + "</double>";
2810 },
2811 /**
2812 * Writes a double tag with the content.
2813 * @param {Number} num the double to encode
2814 */
2815 writeDouble: function(num) {
2816 this.write(this.encodeDouble(num));
2817 },
2818 /**
2819 * Returns an encoded number. Decides wheter to use int or double encoding.
2820 * @param {Number} num the number to encode
2821 */
2822 encodeNumber: function(num) {
2823 var maxInt = 536870911,
2824 minSignedInt = -268435455;
2825 //<debug>
2826 if (typeof (num) !== "number" && !(num instanceof Number)) {
2827 Ext.log.warn("Encoder: writeNumber argument is not numeric. Can't coerce.");
2828 }
2829 // </debug>
2830 // switch to the primitive value for handling:
2831 if (num instanceof Number) {
2832 num = num.valueOf();
2833 }
2834 // Determine if this is an integer or a float.
2835 if (num % 1 === 0 && num >= minSignedInt && num <= maxInt) {
2836 // The number has no decimal point and is within bounds. Let's encode it.
2837 return this.encodeInt(num);
2838 } else {
2839 return this.encodeDouble(num);
2840 }
2841 },
2842 /**
2843 * Writes a number, deciding if to use int or double as the tag
2844 * @param {Number} num the number to encode
2845 */
2846 writeNumber: function(num) {
2847 this.write(this.encodeNumber(num));
2848 },
2849 /**
2850 * Encode a date
2851 * @param {Date} date the date to encode
2852 */
2853 encodeDate: function(date) {
2854 return "<date>" + (new Number(date)).toString() + "</date>";
2855 },
2856 /**
2857 * Write a date to the string
2858 * @param {Date} date the date to encode
2859 */
2860 writeDate: function(date) {
2861 this.write(this.encodeDate(date));
2862 },
2863 /**
2864 * @private
2865 * Encodes one ECMA array element
2866 * @param {String} key the name of the element
2867 * @param {Object} value the value of the element
2868 * @return {String} the encoded key-value pair
2869 */
2870 encodeEcmaElement: function(key, value) {
2871 var str = '<item name="' + key.toString() + '">' + this.encodeObject(value) + '</item>';
2872 return str;
2873 },
2874 /**
2875 * Encodes an array, marking it as an ECMA array if it has associative (non-ordinal) indices
2876 * @param {Array} array the array to encode
2877 */
2878 encodeArray: function(array) {
2879 var ordinals = [],
2880 firstNonOrdinal,
2881 ecmaElements = [],
2882 length = array.length,
2883 // length is of ordinal section only
2884 i, str;
2885 for (i in array) {
2886 if (Ext.isNumeric(i) && (i % 1 == 0)) {
2887 //this is an integer. Add to ordinals array
2888 ordinals[i] = this.encodeObject(array[i]);
2889 } else {
2890 ecmaElements.push(this.encodeEcmaElement(i, array[i]));
2891 }
2892 }
2893 firstNonOrdinal = ordinals.length;
2894 // now, check if we have consecutive numbers in the ordinals array
2895 for (i = 0; i < ordinals.length; i++) {
2896 if (ordinals[i] === undefined) {
2897 // we have a gap in the array. Mark it - the rest of the items become ECMA elements
2898 firstNonOrdinal = i;
2899 break;
2900 }
2901 }
2902 if (firstNonOrdinal < ordinals.length) {
2903 // transfer some of the elements to the ecma array
2904 for (i = firstNonOrdinals; i < ordinals.length; i++) {
2905 if (ordinals[i] !== undefined) {
2906 ecmaElements.push(this.encodeEcmaElement(i, ordinals[i]));
2907 }
2908 }
2909 ordinals = ordinals.slice(0, firstNonOrdinal);
2910 }
2911 // finally start constructing the string
2912 str = '<array length="' + ordinals.length + '"';
2913 if (ecmaElements.length > 0) {
2914 str += ' ecma="true"';
2915 }
2916 str += '>';
2917 // first add the oridnals in consecutive order:
2918 for (i = 0; i < ordinals.length; i++) {
2919 // iterate by counting since we need to guarantee the order
2920 str += ordinals[i];
2921 }
2922 // Now add ECMA items
2923 for (i in ecmaElements) {
2924 str += ecmaElements[i];
2925 }
2926 // And close the array:
2927 str += '</array>';
2928 return str;
2929 },
2930 /**
2931 * Writes an array to the string, marking it as an ECMA array if it has associative (non-ordinal) indices
2932 * @param {Array} array the array to encode
2933 */
2934 writeArray: function(array) {
2935 this.write(this.encodeArray(array));
2936 },
2937 /**
2938 * Encodes an xml document into a CDATA section
2939 * @param {XMLElement/HTMLElement} xml an XML document or element (Document type in some browsers)
2940 */
2941 encodeXml: function(xml) {
2942 var str = this.convertXmlToString(xml);
2943 return "<xml><![CDATA[" + str + "]]></xml>";
2944 },
2945 /**
2946 * Write an XML document to the string
2947 * @param {XMLElement/HTMLElement} xml an XML document or element (Document type in some browsers)
2948 */
2949 writeXml: function(xml) {
2950 this.write(this.encodeXml(xml));
2951 },
2952 /**
2953 * Encodes a generic object into AMFX format. If a <tt>$flexType</tt> member is defined, list that as the object type.
2954 * @param {Object} obj the object to encode
2955 * @return {String} the encoded text
2956 */
2957 encodeGenericObject: function(obj) {
2958 var traits = [],
2959 values = [],
2960 flexType = null,
2961 i, str;
2962 for (i in obj) {
2963 if (i == "$flexType") {
2964 flexType = obj[i];
2965 } else {
2966 traits.push(this.encodeString(new String(i)));
2967 values.push(this.encodeObject(obj[i]));
2968 }
2969 }
2970 if (flexType) {
2971 str = '<object type="' + flexType + '">';
2972 } else {
2973 str = "<object>";
2974 }
2975 if (traits.length > 0) {
2976 str += "<traits>";
2977 str += traits.join("");
2978 str += "</traits>";
2979 } else {
2980 str += "<traits />";
2981 }
2982 str += values.join("");
2983 str += "</object>";
2984 return str;
2985 },
2986 /**
2987 * Writes a generic object to the string. If a <tt>$flexType</tt> member is defined, list that as the object type.
2988 * @param {Object} obj the object to encode
2989 */
2990 writeGenericObject: function(obj) {
2991 this.write(this.encodeGenericObject(obj));
2992 },
2993 /**
2994 * Encodes a byte arrat in AMFX format
2995 * @param {Array} array the byte array to encode
2996 */
2997 encodeByteArray: function(array) {
2998 var str, i, h;
2999 if (array.length > 0) {
3000 str = "<bytearray>";
3001 for (i = 0; i < array.length; i++) {
3002 //<debug>
3003 if (!Ext.isNumber(array[i])) {
3004 Ext.raise("Byte array contains a non-number: " + array[i] + " in index: " + i);
3005 }
3006 if (array[i] < 0 || array[i] > 255) {
3007 Ext.raise("Byte array value out of bounds: " + array[i]);
3008 }
3009 //</debug>
3010 h = array[i].toString(16).toUpperCase();
3011 if (array[i] < 16) {
3012 h = "0" + h;
3013 }
3014 str += h;
3015 }
3016 str += "</bytearray>";
3017 } else {
3018 str = "<bytearray />";
3019 }
3020 return str;
3021 },
3022 /**
3023 * Writes an AMFX byte array to the string. This is for convenience only and is not called automatically by writeObject.
3024 * @param {Array} array the byte array to encode
3025 */
3026 writeByteArray: function(array) {
3027 this.write(this.encodeByteArray(array));
3028 },
3029 /**
3030 * encode the appropriate data item. Supported types:
3031 * - undefined
3032 * - null
3033 * - boolean
3034 * - integer
3035 * - double
3036 * - UTF-8 string
3037 * - XML Document (identified by being instaneof Document. Can be generated with: new DOMParser()).parseFromString(xml, "text/xml");
3038 * - Date
3039 * - Array
3040 * - Generic object
3041 * @param {Object} item A primitive or object to write to the stream
3042 * @return {String} the encoded object in AMFX format
3043 */
3044 encodeObject: function(item) {
3045 var t = typeof (item);
3046 //Ext.log("Writing " + item + " of type " + t);
3047 if (t === "undefined") {
3048 return this.encodeUndefined();
3049 } else if (item === null) {
3050 // can't check type since typeof(null) returns "object"
3051 return this.encodeNull();
3052 } else if (Ext.isBoolean(item)) {
3053 return this.encodeBoolean(item);
3054 } else if (Ext.isString(item)) {
3055 return this.encodeString(item);
3056 } else if (t === "number" || item instanceof Number) {
3057 // Can't use Ext.isNumeric since it accepts strings as well
3058 return this.encodeNumber(item);
3059 } else if (t === "object") {
3060 // Figure out which object this is
3061 if (item instanceof Date) {
3062 return this.encodeDate(item);
3063 } else if (Ext.isArray(item)) {
3064 return this.encodeArray(item);
3065 } else if (this.isXmlDocument(item)) {
3066 return this.encodeXml(item);
3067 } else {
3068 // Treat this as a generic object with name/value pairs of data.
3069 return this.encodeGenericObject(item);
3070 }
3071 } else {
3072 //<debug>
3073 Ext.log.warn("AMFX Encoder: Unknown item type " + t + " can't be written to stream: " + item);
3074 }
3075 //</debug>
3076 return null;
3077 },
3078 // if we reached here, return null
3079 /**
3080 * Writes the appropriate data item to the string. Supported types:
3081 * - undefined
3082 * - null
3083 * - boolean
3084 * - integer
3085 * - double
3086 * - UTF-8 string
3087 * - XML Document (identified by being instaneof Document. Can be generated with: new DOMParser()).parseFromString(xml, "text/xml");
3088 * - Date
3089 * - Array
3090 * - Generic object
3091 * @param {Object} item A primitive or object to write to the stream
3092 */
3093 writeObject: function(item) {
3094 this.write(this.encodeObject(item));
3095 },
3096 /**
3097 * Encodes an AMFX remoting message with the AMFX envelope.
3098 * @param {Ext.data.amf.RemotingMessage} message the message to pass on to serialize.
3099 */
3100 encodeAmfxRemotingPacket: function(message) {
3101 var msg, str;
3102 str = '<amfx ver="3" xmlns="http://www.macromedia.com/2005/amfx"><body>';
3103 str += message.encodeMessage();
3104 str += '</body></amfx>';
3105 return str;
3106 },
3107 /**
3108 * Writes an AMFX remoting message with the AMFX envelope to the string.
3109 * @param {Ext.data.amf.RemotingMessage} message the message to pass on to serialize.
3110 */
3111 writeAmfxRemotingPacket: function(params) {
3112 this.write(this.encodeAmfxRemotingPacket(params));
3113 },
3114 /**
3115 * Converts an XML Document object to a string.
3116 * @param {Object} xml XML document to convert (typically Document object)
3117 * @return {String} A string representing the document
3118 * @private
3119 */
3120 convertXmlToString: function(xml) {
3121 var str;
3122 if (window.XMLSerializer) {
3123 // this is not IE, so:
3124 str = new window.XMLSerializer().serializeToString(xml);
3125 } else {
3126 //no XMLSerializer, might be an old version of IE
3127 str = xml.xml;
3128 }
3129 return str;
3130 },
3131 /**
3132 * Tries to determine if an object is an XML document
3133 * @param {Object} item to identify
3134 * @return {Boolean} true if it's an XML document, false otherwise
3135 */
3136 isXmlDocument: function(item) {
3137 // We can't test if Document is defined since IE just throws an exception. Instead rely on the DOMParser object
3138 if (window.DOMParser) {
3139 if (Ext.isDefined(item.doctype)) {
3140 return true;
3141 }
3142 }
3143 // Otherwise, check if it has an XML field
3144 if (Ext.isString(item.xml)) {
3145 // and we can get the xml
3146 return true;
3147 }
3148 return false;
3149 },
3150 /**
3151 * Appends a string to the body of the message
3152 * @param {String} str the string to append
3153 * @private
3154 */
3155 write: function(str) {
3156 this.body += str;
3157 }
3158 });
3159
3160 // @tag enterprise
3161 /**
3162 * @class Ext.direct.AmfRemotingProvider
3163 *
3164 * <p>The {@link Ext.direct.AmfRemotingProvider AmfRemotingProvider}
3165 * allows making RPC calls to a Java object on a BlazeDS or ColdFusion using either the AMFX or the AMF protocols.</p>
3166 *
3167 * <p>The default protocol is AMFX which works on all browsers. If you choose AMF, a flash plugin might be loaded in certain browsers that do not support posting binary data to the server, e.g. Internet Explorer version 9 or less. To choose AMF, set the {@link Ext.direct.AmfRemotingProvider#binary binary} property to true.</p>
3168 * <p>For AMFX, the server must be configured to expose the desired services via an HTTPEndpoint. For example, the following configuration snippet adds an HTTPEndpoint (AMFX endpoint) to the BlazeDS services-config.xml file:</p>
3169 * <pre><code>
3170 &lt;channel-definition id="my-http" class="mx.messaging.channels.HTTPChannel"&gt;
3171 &lt;endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/http" class="flex.messaging.endpoints.HTTPEndpoint"/&gt;
3172 &lt;/channel-definition&gt;
3173 </code></pre>
3174 *
3175 * <p>Once the HTTPEndpoint is configured, make sure the service is exposed via the channel by adding the channel (e.g. my-http) to your remoting-services.xml file.
3176 * For example this allows services to be accessed remotely by both AMF and AMFX:</p>
3177 * <pre><code>
3178 &lt;default-channels&gt;
3179 &lt;channel ref="my-amf"/&gt;
3180 &lt;channel ref="my-http"/&gt;
3181 &lt;/default-channels&gt;
3182 * </code></pre>
3183 *
3184 * <p>In order to make a call, you first need to declare the API to Ext direct. The following example defines local methods to the services provided by the sample Products application provided by Adobe as part of the BlazeDS 4.x binary turnkey distribution's testdrive (Sample 5: Updating Data):</p>
3185 * <pre><code>
3186 Ext.direct.Manager.addProvider({
3187 "url":"/samples/messagebroker/http", // URL for the HTTPEndpoint
3188 "type":"amfremoting",
3189 "endpoint": "my-http", // the name of the HTTPEndpoint channel as defined in the server's services-config.xml
3190 "actions":{
3191 "product":[{ // name of the destination as defined in remoting-config.xml on the server
3192 "name":"getProducts", // method name of the method to call
3193 "len":0 // number of parameters
3194 },{
3195 "name":"add",
3196 "len":1
3197 },{
3198 "name":"bad",
3199 "len":0
3200 }]
3201 }
3202 });
3203
3204 * </code></pre>
3205 * <p>You can now call the service as follows:</p>
3206 <pre><code>
3207 product.getProducts((function(provider, response) {
3208 // do something with the response
3209 console.log("Got " + response.data.length + " objects");
3210 });
3211 </code></pre>
3212 *
3213 * Note that in case server methods require parameters of a specific class (e.g. flex.samples.product.Product), you should make sure the passed parameter has a field called $flexType set to the class name (in this case flex.Samples.product.Product). This is similar to the remote class alias definition in ActionScript.
3214 *
3215 *
3216 * <p>The following example shows how to define a binary AMF-based call:</p>
3217 * <pre><code>
3218 Ext.direct.Manager.addProvider({
3219 "url":"/samples/messagebroker/amf", // URL for the AMFEndpoint
3220 "type":"amfremoting",
3221 "endpoint": "my-amf", // the name of the AMFEndpoint channel as defined in the server's services-config.xml
3222 "binary": true, // chooses AMF encoding
3223 "actions":{
3224 "product":[{ // name of the destination as defined in remoting-config.xml on the server
3225 "name":"getProducts", // method name of the method to call
3226 "len":0 // number of parameters
3227 },{
3228 "name":"add",
3229 "len":1
3230 },{
3231 "name":"bad",
3232 "len":0
3233 }]
3234 }
3235 });
3236
3237 * </code></pre>
3238 * <p>Calling the server is done the same way as for the AMFX-based definition.</p>
3239
3240 */
3241 Ext.define('Ext.direct.AmfRemotingProvider', {
3242 /* Begin Definitions */
3243 alias: 'direct.amfremotingprovider',
3244 extend: 'Ext.direct.Provider',
3245 requires: [
3246 'Ext.util.MixedCollection',
3247 'Ext.util.DelayedTask',
3248 'Ext.direct.Transaction',
3249 'Ext.direct.RemotingMethod',
3250 'Ext.data.amf.XmlEncoder',
3251 'Ext.data.amf.XmlDecoder',
3252 'Ext.data.amf.Encoder',
3253 'Ext.data.amf.Packet',
3254 'Ext.data.amf.RemotingMessage',
3255 'Ext.direct.ExceptionEvent'
3256 ],
3257 /* End Definitions */
3258 /**
3259 * @cfg {Object} actions
3260 * Object literal defining the server side actions and methods. For example, if
3261 * the Provider is configured with:
3262 * <pre><code>
3263 "actions":{ // each property within the 'actions' object represents a server side Class
3264 "TestAction":[ // array of methods within each server side Class to be
3265 { // stubbed out on client
3266 "name":"doEcho",
3267 "len":1
3268 },{
3269 "name":"multiply",// name of method
3270 "len":2 // The number of parameters that will be used to create an
3271 // array of data to send to the server side function.
3272 // Ensure the server sends back a Number, not a String.
3273 },{
3274 "name":"doForm",
3275 "formHandler":true, // direct the client to use specialized form handling method
3276 "len":1
3277 }]
3278 }
3279 * </code></pre>
3280 * <p>Note that a Store is not required, a server method can be called at any time.
3281 * In the following example a <b>client side</b> handler is used to call the
3282 * server side method "multiply" in the server-side "TestAction" Class:</p>
3283 * <pre><code>
3284 TestAction.multiply(
3285 2, 4, // pass two arguments to server, so specify len=2
3286 // callback function after the server is called
3287 // result: the result returned by the server
3288 // e: Ext.direct.RemotingEvent object
3289 function(result, e) {
3290 var t = e.getTransaction();
3291 var action = t.action; // server side Class called
3292 var method = t.method; // server side method called
3293 if(e.status) {
3294 var answer = Ext.encode(result); // 8
3295
3296 } else {
3297 var msg = e.message; // failure message
3298 }
3299 }
3300 );
3301 * </code></pre>
3302 * In the example above, the server side "multiply" function will be passed two
3303 * arguments (2 and 4). The "multiply" method should return the value 8 which will be
3304 * available as the <tt>result</tt> in the example above.
3305 */
3306 /**
3307 * @cfg {String/Object} namespace
3308 * Namespace for the Remoting Provider (defaults to the browser global scope of <i>window</i>).
3309 * Explicitly specify the namespace Object, or specify a String to have a
3310 * {@link Ext#namespace namespace created} implicitly.
3311 */
3312 /**
3313 * @cfg {String} url
3314 * <b>Required</b>. The URL to connect to the Flex remoting server (LCDS, BlazeDS, etc).
3315 * This should include the /messagebroker/amf suffix as defined in the services-config.xml and remoting-config.xml files.
3316 */
3317 /**
3318 * @cfg {String} endpoint
3319 * <b>Requred</b>. This is the channel id defined in services-config.xml on the server (e.g. my-amf or my-http).
3320 */
3321 /**
3322 * @cfg {String} enableUrlEncode
3323 * Specify which param will hold the arguments for the method.
3324 * Defaults to <tt>'data'</tt>.
3325 */
3326 /**
3327 * @cfg {String} binary
3328 * If true, use AMF binary encoding instead of AMFX XML-based encoding. Note that on some browsers, this will load a flash plugin to handle binary communication with the server. Important: If using binary encoding with older browsers, see notes in {@link Ext.data.flash.BinaryXhr BinaryXhr} regarding packaging the Flash plugin for use in older browsers.
3329 */
3330 binary: false,
3331 /**
3332 * @cfg {Number} maxRetries
3333 * Number of times to re-attempt delivery on failure of a call.
3334 */
3335 maxRetries: 1,
3336 /**
3337 * @cfg {Number} timeout
3338 * The timeout to use for each request.
3339 */
3340 timeout: undefined,
3341 /**
3342 * @event beforecall
3343 * Fires immediately before the client-side sends off the RPC call.
3344 * By returning false from an event handler you can prevent the call from
3345 * executing.
3346 * @param {Ext.direct.AmfRemotingProvider} provider
3347 * @param {Ext.direct.Transaction} transaction
3348 * @param {Object} meta The meta data
3349 */
3350 /**
3351 * @event call
3352 * Fires immediately after the request to the server-side is sent. This does
3353 * NOT fire after the response has come back from the call.
3354 * @param {Ext.direct.AmfRemotingProvider} provider
3355 * @param {Ext.direct.Transaction} transaction
3356 * @param {Object} meta The meta data
3357 */
3358 constructor: function(config) {
3359 var me = this;
3360 me.callParent(arguments);
3361 me.namespace = (Ext.isString(me.namespace)) ? Ext.ns(me.namespace) : me.namespace || window;
3362 me.transactions = new Ext.util.MixedCollection();
3363 me.callBuffer = [];
3364 },
3365 /**
3366 * Initialize the API
3367 * @private
3368 */
3369 initAPI: function() {
3370 var actions = this.actions,
3371 namespace = this.namespace,
3372 action, cls, methods, i, len, method;
3373 for (action in actions) {
3374 if (actions.hasOwnProperty(action)) {
3375 cls = namespace[action];
3376 if (!cls) {
3377 cls = namespace[action] = {};
3378 }
3379 methods = actions[action];
3380 for (i = 0 , len = methods.length; i < len; ++i) {
3381 method = new Ext.direct.RemotingMethod(methods[i]);
3382 cls[method.name] = this.createHandler(action, method);
3383 }
3384 }
3385 }
3386 },
3387 /**
3388 * Create a handler function for a direct call.
3389 * @private
3390 * @param {String} action The action the call is for
3391 * @param {Object} method The details of the method
3392 * @return {Function} A JS function that will kick off the call
3393 */
3394 createHandler: function(action, method) {
3395 var me = this,
3396 handler;
3397 if (!method.formHandler) {
3398 handler = function() {
3399 me.configureRequest(action, method, Array.prototype.slice.call(arguments, 0));
3400 };
3401 } else {
3402 handler = function(form, callback, scope) {
3403 me.configureFormRequest(action, method, form, callback, scope);
3404 };
3405 }
3406 handler.directCfg = {
3407 action: action,
3408 method: method
3409 };
3410 return handler;
3411 },
3412 isConnected: function() {
3413 return !!this.connected;
3414 },
3415 connect: function() {
3416 var me = this;
3417 if (me.url) {
3418 // Generate a unique ID for this client
3419 me.clientId = Ext.data.amf.XmlEncoder.generateFlexUID();
3420 me.initAPI();
3421 me.connected = true;
3422 me.fireEvent('connect', me);
3423 me.DSId = null;
3424 } else if (!me.url) {
3425 //<debug>
3426 Ext.raise('Error initializing RemotingProvider, no url configured.');
3427 }
3428 },
3429 //</debug>
3430 disconnect: function() {
3431 var me = this;
3432 if (me.connected) {
3433 me.connected = false;
3434 me.fireEvent('disconnect', me);
3435 }
3436 },
3437 /**
3438 * Run any callbacks related to the transaction.
3439 * @private
3440 * @param {Ext.direct.Transaction} transaction The transaction
3441 * @param {Ext.direct.Event} event The event
3442 */
3443 runCallback: function(transaction, event) {
3444 var success = !!event.status,
3445 funcName = success ? 'success' : 'failure',
3446 callback, result;
3447 if (transaction && transaction.callback) {
3448 callback = transaction.callback;
3449 result = Ext.isDefined(event.result) ? event.result : event.data;
3450 if (Ext.isFunction(callback)) {
3451 callback(result, event, success);
3452 } else {
3453 Ext.callback(callback[funcName], callback.scope, [
3454 result,
3455 event,
3456 success
3457 ]);
3458 Ext.callback(callback.callback, callback.scope, [
3459 result,
3460 event,
3461 success
3462 ]);
3463 }
3464 }
3465 },
3466 /**
3467 * React to the ajax request being completed
3468 * @private
3469 */
3470 onData: function(options, success, response) {
3471 var me = this,
3472 i = 0,
3473 len, events, event, transaction, transactions;
3474 if (success) {
3475 events = me.createEvents(response);
3476 for (len = events.length; i < len; ++i) {
3477 event = events[i];
3478 transaction = me.getTransaction(event);
3479 me.fireEvent('data', me, event);
3480 if (transaction) {
3481 me.runCallback(transaction, event, true);
3482 Ext.direct.Manager.removeTransaction(transaction);
3483 }
3484 }
3485 } else {
3486 transactions = [].concat(options.transaction);
3487 for (len = transactions.length; i < len; ++i) {
3488 transaction = me.getTransaction(transactions[i]);
3489 if (transaction && transaction.retryCount < me.maxRetries) {
3490 transaction.retry();
3491 } else {
3492 event = new Ext.direct.ExceptionEvent({
3493 data: null,
3494 transaction: transaction,
3495 code: Ext.direct.Manager.exceptions.TRANSPORT,
3496 message: 'Unable to connect to the server.',
3497 xhr: response
3498 });
3499 me.fireEvent('data', me, event);
3500 if (transaction) {
3501 me.runCallback(transaction, event, false);
3502 Ext.direct.Manager.removeTransaction(transaction);
3503 }
3504 }
3505 }
3506 }
3507 },
3508 /**
3509 * Get transaction from XHR options
3510 * @private
3511 * @param {Object} options The options sent to the Ajax request
3512 * @return {Ext.direct.Transaction} The transaction, null if not found
3513 */
3514 getTransaction: function(options) {
3515 return options && options.tid ? Ext.direct.Manager.getTransaction(options.tid) : null;
3516 },
3517 /**
3518 * Configure a direct request
3519 * @private
3520 * @param {String} action The action being executed
3521 * @param {Object} method The method being executed
3522 */
3523 configureRequest: function(action, method, args) {
3524 var me = this,
3525 callData = method.getCallData(args),
3526 data = callData.data,
3527 callback = callData.callback,
3528 scope = callData.scope,
3529 transaction;
3530 transaction = new Ext.direct.Transaction({
3531 provider: me,
3532 args: args,
3533 action: action,
3534 method: method.name,
3535 data: data,
3536 callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback
3537 });
3538 if (me.fireEvent('beforecall', me, transaction, method) !== false) {
3539 Ext.direct.Manager.addTransaction(transaction);
3540 me.queueTransaction(transaction);
3541 me.fireEvent('call', me, transaction, method);
3542 }
3543 },
3544 /**
3545 * Gets the Flex remoting message info for a transaction
3546 * @private
3547 * @param {Ext.direct.Transaction} transaction The transaction
3548 * @return {Object} The Flex remoting message structure ready to encode in an AMFX RemoteMessage
3549 */
3550 getCallData: function(transaction) {
3551 if (this.binary) {
3552 return {
3553 targetUri: transaction.action + "." + transaction.method,
3554 responseUri: '/' + transaction.id,
3555 body: transaction.data || []
3556 };
3557 } else {
3558 return new Ext.data.amf.RemotingMessage({
3559 body: transaction.data || [],
3560 clientId: this.clientId,
3561 destination: transaction.action,
3562 headers: {
3563 DSEndpoint: this.endpoint,
3564 DSId: this.DSId || "nil"
3565 },
3566 // if unknown yet, use "nil"
3567 messageId: Ext.data.amf.XmlEncoder.generateFlexUID(transaction.id),
3568 // encode as first 4 bytes of UID
3569 operation: transaction.method,
3570 timestamp: 0,
3571 timeToLive: 0
3572 });
3573 }
3574 },
3575 /*
3576 return {
3577 action: transaction.action,
3578 method: transaction.method,
3579 data: transaction.data,
3580 type: 'rpc',
3581 tid: transaction.id
3582 };
3583 */
3584 /**
3585 * Sends a request to the server
3586 * @private
3587 * @param {Object/Array} data The data to send
3588 */
3589 sendRequest: function(data) {
3590 var me = this,
3591 request = {
3592 url: me.url,
3593 callback: me.onData,
3594 scope: me,
3595 transaction: data,
3596 timeout: me.timeout
3597 },
3598 callData,
3599 i = 0,
3600 len, params, encoder,
3601 amfMessages = [],
3602 amfHeaders = [];
3603 // prepare AMFX messages
3604 if (Ext.isArray(data)) {
3605 //<debug>
3606 if (!me.binary) {
3607 Ext.raise("Mutltiple messages in the same call are not supported in AMFX");
3608 }
3609 //</debug>
3610 for (len = data.length; i < len; ++i) {
3611 amfMessages.push(me.getCallData(data[i]));
3612 }
3613 } else {
3614 amfMessages.push(me.getCallData(data));
3615 }
3616 if (me.binary) {
3617 encoder = new Ext.data.amf.Encoder({
3618 format: 0
3619 });
3620 // AMF message sending always uses AMF0
3621 // encode packet
3622 encoder.writeAmfPacket(amfHeaders, amfMessages);
3623 request.binaryData = encoder.bytes;
3624 request.binary = true;
3625 // Binary response
3626 request.headers = {
3627 'Content-Type': 'application/x-amf'
3628 };
3629 } else {
3630 encoder = new Ext.data.amf.XmlEncoder();
3631 // encode packet
3632 encoder.writeAmfxRemotingPacket(amfMessages[0]);
3633 request.xmlData = encoder.body;
3634 }
3635 // prepare Ajax request
3636 Ext.Ajax.request(request);
3637 },
3638 /**
3639 * Add a new transaction to the queue
3640 * @private
3641 * @param {Ext.direct.Transaction} transaction The transaction
3642 */
3643 queueTransaction: function(transaction) {
3644 var me = this,
3645 enableBuffer = false;
3646 // no queueing for AMFX
3647 if (transaction.form) {
3648 me.sendFormRequest(transaction);
3649 return;
3650 }
3651 me.callBuffer.push(transaction);
3652 if (enableBuffer) {
3653 if (!me.callTask) {
3654 me.callTask = new Ext.util.DelayedTask(me.combineAndSend, me);
3655 }
3656 me.callTask.delay(Ext.isNumber(enableBuffer) ? enableBuffer : 10);
3657 } else {
3658 me.combineAndSend();
3659 }
3660 },
3661 /**
3662 * Combine any buffered requests and send them off
3663 * @private
3664 */
3665 combineAndSend: function() {
3666 var buffer = this.callBuffer,
3667 len = buffer.length;
3668 if (len > 0) {
3669 this.sendRequest(len == 1 ? buffer[0] : buffer);
3670 this.callBuffer = [];
3671 }
3672 },
3673 /**
3674 * Configure a form submission request
3675 * @private
3676 * @param {String} action The action being executed
3677 * @param {Object} method The method being executed
3678 * @param {HTMLElement} form The form being submitted
3679 * @param {Function} callback (optional) A callback to run after the form submits
3680 * @param {Object} scope (optional) A scope to execute the callback in
3681 */
3682 configureFormRequest: function(action, method, form, callback, scope) {
3683 //<debug>
3684 Ext.raise("Form requests are not supported for AmfRemoting");
3685 },
3686 //</debug>
3687 /*
3688 var me = this,
3689 transaction = new Ext.direct.Transaction({
3690 provider: me,
3691 action: action,
3692 method: method.name,
3693 args: [form, callback, scope],
3694 callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback,
3695 isForm: true
3696 }),
3697 isUpload,
3698 params;
3699
3700 if (me.fireEvent('beforecall', me, transaction, method) !== false) {
3701 Ext.direct.Manager.addTransaction(transaction);
3702 isUpload = String(form.getAttribute("enctype")).toLowerCase() == 'multipart/form-data';
3703
3704 params = {
3705 extTID: transaction.id,
3706 extAction: action,
3707 extMethod: method.name,
3708 extType: 'rpc',
3709 extUpload: String(isUpload)
3710 };
3711
3712 // change made from typeof callback check to callback.params
3713 // to support addl param passing in DirectSubmit EAC 6/2
3714 Ext.apply(transaction, {
3715 form: Ext.getDom(form),
3716 isUpload: isUpload,
3717 params: callback && Ext.isObject(callback.params) ? Ext.apply(params, callback.params) : params
3718 });
3719 me.fireEvent('call', me, transaction, method);
3720 me.sendFormRequest(transaction);
3721 }
3722 */
3723 /**
3724 * Sends a form request
3725 * @private
3726 * @param {Ext.direct.Transaction} transaction The transaction to send
3727 */
3728 sendFormRequest: function(transaction) {
3729 //<debug>
3730 Ext.raise("Form requests are not supported for AmfRemoting");
3731 },
3732 //</debug>
3733 /*
3734 Ext.Ajax.request({
3735 url: this.url,
3736 params: transaction.params,
3737 callback: this.onData,
3738 scope: this,
3739 form: transaction.form,
3740 isUpload: transaction.isUpload,
3741 transaction: transaction
3742 });
3743 */
3744 /**
3745 * Creates a set of events based on the XHR response
3746 * @private
3747 * @param {Object} response The XHR response
3748 * @return {Ext.direct.Event[]} An array of Ext.direct.Event
3749 */
3750 createEvents: function(response) {
3751 var data = null,
3752 rawBytes = [],
3753 events = [],
3754 event,
3755 i = 0,
3756 len, decoder;
3757 try {
3758 if (this.binary) {
3759 decoder = new Ext.data.amf.Packet();
3760 data = decoder.decode(response.responseBytes);
3761 } else {
3762 decoder = new Ext.data.amf.XmlDecoder();
3763 data = decoder.readAmfxMessage(response.responseText);
3764 }
3765 } /*
3766 // This won't be sent back unless we use a ping message, so ignore for now
3767 // if we don't have the server ID yet, check for it here
3768 if (!this.DSId) {
3769 if (data.message.headers && data.message.headers.DSId) {
3770 this.DSId = data.message.headers.DSId;
3771 }
3772 }
3773 */
3774 catch (e) {
3775 event = new Ext.direct.ExceptionEvent({
3776 data: e,
3777 xhr: response,
3778 code: Ext.direct.Manager.exceptions.PARSE,
3779 message: 'Error parsing AMF response: \n\n ' + data
3780 });
3781 return [
3782 event
3783 ];
3784 }
3785 if (this.binary) {
3786 for (i = 0; i < data.messages.length; i++) {
3787 events.push(this.createEvent(data.messages[i]));
3788 }
3789 } else {
3790 // AMFX messages have one response per message
3791 events.push(this.createEvent(data));
3792 }
3793 return events;
3794 },
3795 /**
3796 * Create an event from an AMFX response object
3797 * @param {Object} response The AMFX response object
3798 * @return {Ext.direct.Event} The event
3799 */
3800 createEvent: function(response) {
3801 // Check targetUri to identify transaction ID and status
3802 var status = response.targetURI.split("/"),
3803 tid, event, data, statusIndex,
3804 me = this;
3805 if (me.binary) {
3806 tid = status[1];
3807 statusIndex = 2;
3808 } else {
3809 tid = Ext.data.amf.XmlDecoder.decodeTidFromFlexUID(response.message.correlationId);
3810 statusIndex = 1;
3811 }
3812 // construct data structure
3813 if (status[statusIndex] == "onStatus") {
3814 // The call failed
3815 data = {
3816 tid: tid,
3817 data: (me.binary ? response.body : response.message)
3818 };
3819 event = Ext.create('direct.exception', data);
3820 } else if (status[statusIndex] == "onResult") {
3821 // Call succeeded
3822 data = {
3823 tid: tid,
3824 data: (me.binary ? response.body : response.message.body)
3825 };
3826 event = Ext.create('direct.rpc', data);
3827 } else {
3828 //<debug>
3829 Ext.raise("Unknown AMF return status: " + status[statusIndex]);
3830 }
3831 //</debug>
3832 return event;
3833 }
3834 });
3835