@tomgiee/tsdp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +124 -0
  3. package/dist/src/builder/media-builder.d.ts +221 -0
  4. package/dist/src/builder/media-builder.d.ts.map +1 -0
  5. package/dist/src/builder/media-builder.js +385 -0
  6. package/dist/src/builder/session-builder.d.ts +195 -0
  7. package/dist/src/builder/session-builder.d.ts.map +1 -0
  8. package/dist/src/builder/session-builder.js +366 -0
  9. package/dist/src/index.d.ts +67 -0
  10. package/dist/src/index.d.ts.map +1 -0
  11. package/dist/src/index.js +250 -0
  12. package/dist/src/parser/attribute-parser.d.ts +100 -0
  13. package/dist/src/parser/attribute-parser.d.ts.map +1 -0
  14. package/dist/src/parser/attribute-parser.js +217 -0
  15. package/dist/src/parser/field-parser.d.ts +124 -0
  16. package/dist/src/parser/field-parser.d.ts.map +1 -0
  17. package/dist/src/parser/field-parser.js +335 -0
  18. package/dist/src/parser/media-parser.d.ts +45 -0
  19. package/dist/src/parser/media-parser.d.ts.map +1 -0
  20. package/dist/src/parser/media-parser.js +157 -0
  21. package/dist/src/parser/primitive-parser.d.ts +138 -0
  22. package/dist/src/parser/primitive-parser.d.ts.map +1 -0
  23. package/dist/src/parser/primitive-parser.js +316 -0
  24. package/dist/src/parser/scanner.d.ts +142 -0
  25. package/dist/src/parser/scanner.d.ts.map +1 -0
  26. package/dist/src/parser/scanner.js +284 -0
  27. package/dist/src/parser/session-parser.d.ts +35 -0
  28. package/dist/src/parser/session-parser.d.ts.map +1 -0
  29. package/dist/src/parser/session-parser.js +207 -0
  30. package/dist/src/parser/time-parser.d.ts +74 -0
  31. package/dist/src/parser/time-parser.d.ts.map +1 -0
  32. package/dist/src/parser/time-parser.js +168 -0
  33. package/dist/src/serializer/attribute-serializer.d.ts +18 -0
  34. package/dist/src/serializer/attribute-serializer.d.ts.map +1 -0
  35. package/dist/src/serializer/attribute-serializer.js +34 -0
  36. package/dist/src/serializer/field-serializer.d.ts +112 -0
  37. package/dist/src/serializer/field-serializer.d.ts.map +1 -0
  38. package/dist/src/serializer/field-serializer.js +212 -0
  39. package/dist/src/serializer/media-serializer.d.ts +31 -0
  40. package/dist/src/serializer/media-serializer.d.ts.map +1 -0
  41. package/dist/src/serializer/media-serializer.js +83 -0
  42. package/dist/src/serializer/session-serializer.d.ts +29 -0
  43. package/dist/src/serializer/session-serializer.d.ts.map +1 -0
  44. package/dist/src/serializer/session-serializer.js +99 -0
  45. package/dist/src/serializer/time-serializer.d.ts +46 -0
  46. package/dist/src/serializer/time-serializer.d.ts.map +1 -0
  47. package/dist/src/serializer/time-serializer.js +86 -0
  48. package/dist/src/types/attributes.d.ts +318 -0
  49. package/dist/src/types/attributes.d.ts.map +1 -0
  50. package/dist/src/types/attributes.js +225 -0
  51. package/dist/src/types/errors.d.ts +129 -0
  52. package/dist/src/types/errors.d.ts.map +1 -0
  53. package/dist/src/types/errors.js +186 -0
  54. package/dist/src/types/fields.d.ts +100 -0
  55. package/dist/src/types/fields.d.ts.map +1 -0
  56. package/dist/src/types/fields.js +48 -0
  57. package/dist/src/types/media.d.ts +148 -0
  58. package/dist/src/types/media.d.ts.map +1 -0
  59. package/dist/src/types/media.js +137 -0
  60. package/dist/src/types/network.d.ts +136 -0
  61. package/dist/src/types/network.d.ts.map +1 -0
  62. package/dist/src/types/network.js +130 -0
  63. package/dist/src/types/primitives.d.ts +193 -0
  64. package/dist/src/types/primitives.d.ts.map +1 -0
  65. package/dist/src/types/primitives.js +195 -0
  66. package/dist/src/types/session.d.ts +122 -0
  67. package/dist/src/types/session.d.ts.map +1 -0
  68. package/dist/src/types/session.js +81 -0
  69. package/dist/src/types/time.d.ts +129 -0
  70. package/dist/src/types/time.d.ts.map +1 -0
  71. package/dist/src/types/time.js +84 -0
  72. package/dist/src/utils/address-parser.d.ts +100 -0
  73. package/dist/src/utils/address-parser.d.ts.map +1 -0
  74. package/dist/src/utils/address-parser.js +338 -0
  75. package/dist/src/utils/format-validators.d.ts +77 -0
  76. package/dist/src/utils/format-validators.d.ts.map +1 -0
  77. package/dist/src/utils/format-validators.js +504 -0
  78. package/dist/src/utils/line-reader.d.ts +84 -0
  79. package/dist/src/utils/line-reader.d.ts.map +1 -0
  80. package/dist/src/utils/line-reader.js +169 -0
  81. package/dist/src/utils/time-converter.d.ts +99 -0
  82. package/dist/src/utils/time-converter.d.ts.map +1 -0
  83. package/dist/src/utils/time-converter.js +195 -0
  84. package/dist/src/validator/media-validator.d.ts +27 -0
  85. package/dist/src/validator/media-validator.d.ts.map +1 -0
  86. package/dist/src/validator/media-validator.js +241 -0
  87. package/dist/src/validator/semantic-validator.d.ts +47 -0
  88. package/dist/src/validator/semantic-validator.d.ts.map +1 -0
  89. package/dist/src/validator/semantic-validator.js +207 -0
  90. package/dist/src/validator/session-validator.d.ts +36 -0
  91. package/dist/src/validator/session-validator.d.ts.map +1 -0
  92. package/dist/src/validator/session-validator.js +280 -0
  93. package/dist/tests/integration/round-trip.test.d.ts +5 -0
  94. package/dist/tests/integration/round-trip.test.d.ts.map +1 -0
  95. package/dist/tests/integration/round-trip.test.js +320 -0
  96. package/dist/tests/integration/voip-examples.test.d.ts +5 -0
  97. package/dist/tests/integration/voip-examples.test.d.ts.map +1 -0
  98. package/dist/tests/integration/voip-examples.test.js +361 -0
  99. package/dist/tests/unit/builder/media-builder.test.d.ts +5 -0
  100. package/dist/tests/unit/builder/media-builder.test.d.ts.map +1 -0
  101. package/dist/tests/unit/builder/media-builder.test.js +524 -0
  102. package/dist/tests/unit/builder/session-builder.test.d.ts +5 -0
  103. package/dist/tests/unit/builder/session-builder.test.d.ts.map +1 -0
  104. package/dist/tests/unit/builder/session-builder.test.js +367 -0
  105. package/dist/tests/unit/parser/attribute-parser.test.d.ts +5 -0
  106. package/dist/tests/unit/parser/attribute-parser.test.d.ts.map +1 -0
  107. package/dist/tests/unit/parser/attribute-parser.test.js +319 -0
  108. package/dist/tests/unit/parser/field-parser.test.d.ts +5 -0
  109. package/dist/tests/unit/parser/field-parser.test.d.ts.map +1 -0
  110. package/dist/tests/unit/parser/field-parser.test.js +355 -0
  111. package/dist/tests/unit/parser/media-parser.test.d.ts +5 -0
  112. package/dist/tests/unit/parser/media-parser.test.d.ts.map +1 -0
  113. package/dist/tests/unit/parser/media-parser.test.js +241 -0
  114. package/dist/tests/unit/parser/primitive-parser.test.d.ts +5 -0
  115. package/dist/tests/unit/parser/primitive-parser.test.d.ts.map +1 -0
  116. package/dist/tests/unit/parser/primitive-parser.test.js +261 -0
  117. package/dist/tests/unit/parser/scanner.test.d.ts +5 -0
  118. package/dist/tests/unit/parser/scanner.test.d.ts.map +1 -0
  119. package/dist/tests/unit/parser/scanner.test.js +241 -0
  120. package/dist/tests/unit/parser/session-parser.test.d.ts +5 -0
  121. package/dist/tests/unit/parser/session-parser.test.d.ts.map +1 -0
  122. package/dist/tests/unit/parser/session-parser.test.js +346 -0
  123. package/dist/tests/unit/parser/time-parser.test.d.ts +5 -0
  124. package/dist/tests/unit/parser/time-parser.test.d.ts.map +1 -0
  125. package/dist/tests/unit/parser/time-parser.test.js +173 -0
  126. package/dist/tests/unit/serializer/attribute-serializer.test.d.ts +5 -0
  127. package/dist/tests/unit/serializer/attribute-serializer.test.d.ts.map +1 -0
  128. package/dist/tests/unit/serializer/attribute-serializer.test.js +78 -0
  129. package/dist/tests/unit/serializer/field-serializer.test.d.ts +5 -0
  130. package/dist/tests/unit/serializer/field-serializer.test.d.ts.map +1 -0
  131. package/dist/tests/unit/serializer/field-serializer.test.js +159 -0
  132. package/dist/tests/unit/serializer/media-serializer.test.d.ts +5 -0
  133. package/dist/tests/unit/serializer/media-serializer.test.d.ts.map +1 -0
  134. package/dist/tests/unit/serializer/media-serializer.test.js +155 -0
  135. package/dist/tests/unit/serializer/session-serializer.test.d.ts +5 -0
  136. package/dist/tests/unit/serializer/session-serializer.test.d.ts.map +1 -0
  137. package/dist/tests/unit/serializer/session-serializer.test.js +317 -0
  138. package/dist/tests/unit/serializer/time-serializer.test.d.ts +5 -0
  139. package/dist/tests/unit/serializer/time-serializer.test.d.ts.map +1 -0
  140. package/dist/tests/unit/serializer/time-serializer.test.js +115 -0
  141. package/dist/tests/unit/types/errors.test.d.ts +5 -0
  142. package/dist/tests/unit/types/errors.test.d.ts.map +1 -0
  143. package/dist/tests/unit/types/errors.test.js +127 -0
  144. package/dist/tests/unit/types/network.test.d.ts +5 -0
  145. package/dist/tests/unit/types/network.test.d.ts.map +1 -0
  146. package/dist/tests/unit/types/network.test.js +132 -0
  147. package/dist/tests/unit/types/primitives.test.d.ts +5 -0
  148. package/dist/tests/unit/types/primitives.test.d.ts.map +1 -0
  149. package/dist/tests/unit/types/primitives.test.js +108 -0
  150. package/dist/tests/unit/utils/address-parser.test.d.ts +5 -0
  151. package/dist/tests/unit/utils/address-parser.test.d.ts.map +1 -0
  152. package/dist/tests/unit/utils/address-parser.test.js +203 -0
  153. package/dist/tests/unit/utils/format-validators.test.d.ts +5 -0
  154. package/dist/tests/unit/utils/format-validators.test.d.ts.map +1 -0
  155. package/dist/tests/unit/utils/format-validators.test.js +224 -0
  156. package/dist/tests/unit/utils/line-reader.test.d.ts +5 -0
  157. package/dist/tests/unit/utils/line-reader.test.d.ts.map +1 -0
  158. package/dist/tests/unit/utils/line-reader.test.js +157 -0
  159. package/dist/tests/unit/utils/time-converter.test.d.ts +5 -0
  160. package/dist/tests/unit/utils/time-converter.test.d.ts.map +1 -0
  161. package/dist/tests/unit/utils/time-converter.test.js +190 -0
  162. package/dist/tests/unit/validator/media-validator.test.d.ts +5 -0
  163. package/dist/tests/unit/validator/media-validator.test.d.ts.map +1 -0
  164. package/dist/tests/unit/validator/media-validator.test.js +313 -0
  165. package/dist/tests/unit/validator/semantic-validator.test.d.ts +5 -0
  166. package/dist/tests/unit/validator/semantic-validator.test.d.ts.map +1 -0
  167. package/dist/tests/unit/validator/semantic-validator.test.js +262 -0
  168. package/dist/tests/unit/validator/session-validator.test.d.ts +5 -0
  169. package/dist/tests/unit/validator/session-validator.test.d.ts.map +1 -0
  170. package/dist/tests/unit/validator/session-validator.test.js +447 -0
  171. package/package.json +50 -0
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Session serializer for SDP (RFC 8866)
3
+ *
4
+ * Main entry point for serializing complete SDP session descriptions.
5
+ */
6
+ import { SessionDescription } from '../types/session';
7
+ /**
8
+ * Serialize a complete SDP session description
9
+ *
10
+ * RFC 8866 Section 5 defines the exact ordering of fields:
11
+ * 1. v= (version) [required, exactly one]
12
+ * 2. o= (origin) [required, exactly one]
13
+ * 3. s= (session name) [required, exactly one]
14
+ * 4. i= (session information) [optional, at most one]
15
+ * 5. u= (URI) [optional, at most one]
16
+ * 6. e= (email) [optional, multiple]
17
+ * 7. p= (phone) [optional, multiple]
18
+ * 8. c= (connection) [optional, at most one at session level]
19
+ * 9. b= (bandwidth) [optional, multiple]
20
+ * 10. Time descriptions [required, at least one]
21
+ * 11. k= (key) [optional, at most one]
22
+ * 12. a= (attributes) [optional, multiple]
23
+ * 13. Media descriptions [optional, zero or more]
24
+ *
25
+ * @param session - SessionDescription object
26
+ * @returns Serialized SDP text with CRLF line endings
27
+ */
28
+ export declare function serializeSessionDescription(session: SessionDescription): string;
29
+ //# sourceMappingURL=session-serializer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-serializer.d.ts","sourceRoot":"","sources":["../../../src/serializer/session-serializer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAqBtD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAgF/E"}
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ /**
3
+ * Session serializer for SDP (RFC 8866)
4
+ *
5
+ * Main entry point for serializing complete SDP session descriptions.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.serializeSessionDescription = serializeSessionDescription;
9
+ const field_serializer_1 = require("./field-serializer");
10
+ const time_serializer_1 = require("./time-serializer");
11
+ const attribute_serializer_1 = require("./attribute-serializer");
12
+ const media_serializer_1 = require("./media-serializer");
13
+ // ============================================================================
14
+ // Session Serializer
15
+ // ============================================================================
16
+ /**
17
+ * Serialize a complete SDP session description
18
+ *
19
+ * RFC 8866 Section 5 defines the exact ordering of fields:
20
+ * 1. v= (version) [required, exactly one]
21
+ * 2. o= (origin) [required, exactly one]
22
+ * 3. s= (session name) [required, exactly one]
23
+ * 4. i= (session information) [optional, at most one]
24
+ * 5. u= (URI) [optional, at most one]
25
+ * 6. e= (email) [optional, multiple]
26
+ * 7. p= (phone) [optional, multiple]
27
+ * 8. c= (connection) [optional, at most one at session level]
28
+ * 9. b= (bandwidth) [optional, multiple]
29
+ * 10. Time descriptions [required, at least one]
30
+ * 11. k= (key) [optional, at most one]
31
+ * 12. a= (attributes) [optional, multiple]
32
+ * 13. Media descriptions [optional, zero or more]
33
+ *
34
+ * @param session - SessionDescription object
35
+ * @returns Serialized SDP text with CRLF line endings
36
+ */
37
+ function serializeSessionDescription(session) {
38
+ const lines = [];
39
+ // ========================================================================
40
+ // 1-3. Required session-level fields (v=, o=, s=)
41
+ // ========================================================================
42
+ lines.push((0, field_serializer_1.serializeVersionField)(session.version));
43
+ lines.push((0, field_serializer_1.serializeOriginField)(session.origin));
44
+ lines.push((0, field_serializer_1.serializeSessionNameField)(session.sessionName));
45
+ // ========================================================================
46
+ // 4-9. Optional session-level fields (i=, u=, e=, p=, c=, b=)
47
+ // ========================================================================
48
+ // i= session information (optional)
49
+ if (session.sessionInformation !== undefined) {
50
+ lines.push((0, field_serializer_1.serializeInformationField)(session.sessionInformation));
51
+ }
52
+ // u= URI (optional)
53
+ if (session.uri !== undefined) {
54
+ lines.push((0, field_serializer_1.serializeUriField)(session.uri));
55
+ }
56
+ // e= email (optional, multiple)
57
+ for (const email of session.emails) {
58
+ lines.push((0, field_serializer_1.serializeEmailField)(email));
59
+ }
60
+ // p= phone (optional, multiple)
61
+ for (const phone of session.phones) {
62
+ lines.push((0, field_serializer_1.serializePhoneField)(phone));
63
+ }
64
+ // c= connection (optional at session level)
65
+ if (session.connection !== undefined) {
66
+ lines.push((0, field_serializer_1.serializeConnectionField)(session.connection));
67
+ }
68
+ // b= bandwidth (optional, multiple)
69
+ for (const bandwidth of session.bandwidths) {
70
+ lines.push((0, field_serializer_1.serializeBandwidthField)(bandwidth));
71
+ }
72
+ // ========================================================================
73
+ // 10. Time descriptions (required, at least one)
74
+ // ========================================================================
75
+ for (const td of session.timeDescriptions) {
76
+ lines.push(...(0, time_serializer_1.serializeTimeDescription)(td));
77
+ }
78
+ // ========================================================================
79
+ // 11-12. Session-level key and attributes (k=, a=)
80
+ // ========================================================================
81
+ // k= key (optional, OBSOLETE)
82
+ if (session.key !== undefined) {
83
+ lines.push((0, field_serializer_1.serializeKeyField)(session.key));
84
+ }
85
+ // a= attributes (optional, multiple)
86
+ for (const attr of session.attributes) {
87
+ lines.push((0, attribute_serializer_1.serializeAttributeField)(attr));
88
+ }
89
+ // ========================================================================
90
+ // 13. Media descriptions (optional, zero or more)
91
+ // ========================================================================
92
+ for (const md of session.mediaDescriptions) {
93
+ lines.push(...(0, media_serializer_1.serializeMediaDescription)(md));
94
+ }
95
+ // ========================================================================
96
+ // Join with CRLF and add trailing CRLF
97
+ // ========================================================================
98
+ return lines.join('\r\n') + '\r\n';
99
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Time serializers for SDP (RFC 8866)
3
+ *
4
+ * Serializers for time description fields (t=, r=, z=).
5
+ */
6
+ import { TimeDescription, Timing, Repeat, Timezone } from '../types/time';
7
+ /**
8
+ * Serialize timing field
9
+ *
10
+ * Format: t=<start-time> <stop-time>
11
+ *
12
+ * @param timing - Timing object
13
+ * @returns Serialized timing line (without line ending)
14
+ */
15
+ export declare function serializeTimingField(timing: Timing): string;
16
+ /**
17
+ * Serialize repeat field
18
+ *
19
+ * Format: r=<repeat-interval> <active-duration> <offsets-from-start-time>
20
+ *
21
+ * @param repeat - Repeat object
22
+ * @returns Serialized repeat line (without line ending)
23
+ */
24
+ export declare function serializeRepeatField(repeat: Repeat): string;
25
+ /**
26
+ * Serialize timezone field
27
+ *
28
+ * Format: z=<adjustment-time> <offset> [<adjustment-time> <offset> ...]
29
+ *
30
+ * @param timezone - Timezone object
31
+ * @returns Serialized timezone line (without line ending)
32
+ */
33
+ export declare function serializeTimezoneField(timezone: Timezone): string;
34
+ /**
35
+ * Serialize a complete time description
36
+ *
37
+ * Outputs:
38
+ * - One t= line
39
+ * - Zero or more r= lines
40
+ * - Zero or one z= line
41
+ *
42
+ * @param td - TimeDescription object
43
+ * @returns Array of serialized lines (without line endings)
44
+ */
45
+ export declare function serializeTimeDescription(td: TimeDescription): string[];
46
+ //# sourceMappingURL=time-serializer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-serializer.d.ts","sourceRoot":"","sources":["../../../src/serializer/time-serializer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAO1E;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE3D;AAMD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAM3D;AAMD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAMjE;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,eAAe,GAAG,MAAM,EAAE,CAiBtE"}
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ /**
3
+ * Time serializers for SDP (RFC 8866)
4
+ *
5
+ * Serializers for time description fields (t=, r=, z=).
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.serializeTimingField = serializeTimingField;
9
+ exports.serializeRepeatField = serializeRepeatField;
10
+ exports.serializeTimezoneField = serializeTimezoneField;
11
+ exports.serializeTimeDescription = serializeTimeDescription;
12
+ const time_converter_1 = require("../utils/time-converter");
13
+ // ============================================================================
14
+ // Timing Field Serializer (t=)
15
+ // ============================================================================
16
+ /**
17
+ * Serialize timing field
18
+ *
19
+ * Format: t=<start-time> <stop-time>
20
+ *
21
+ * @param timing - Timing object
22
+ * @returns Serialized timing line (without line ending)
23
+ */
24
+ function serializeTimingField(timing) {
25
+ return `t=${timing.startTime} ${timing.stopTime}`;
26
+ }
27
+ // ============================================================================
28
+ // Repeat Field Serializer (r=)
29
+ // ============================================================================
30
+ /**
31
+ * Serialize repeat field
32
+ *
33
+ * Format: r=<repeat-interval> <active-duration> <offsets-from-start-time>
34
+ *
35
+ * @param repeat - Repeat object
36
+ * @returns Serialized repeat line (without line ending)
37
+ */
38
+ function serializeRepeatField(repeat) {
39
+ const interval = (0, time_converter_1.serializeTypedTime)(repeat.interval);
40
+ const duration = (0, time_converter_1.serializeTypedTime)(repeat.duration);
41
+ const offsets = repeat.offsets.map(time_converter_1.serializeTypedTime).join(' ');
42
+ return `r=${interval} ${duration} ${offsets}`;
43
+ }
44
+ // ============================================================================
45
+ // Timezone Field Serializer (z=)
46
+ // ============================================================================
47
+ /**
48
+ * Serialize timezone field
49
+ *
50
+ * Format: z=<adjustment-time> <offset> [<adjustment-time> <offset> ...]
51
+ *
52
+ * @param timezone - Timezone object
53
+ * @returns Serialized timezone line (without line ending)
54
+ */
55
+ function serializeTimezoneField(timezone) {
56
+ const pairs = timezone.adjustments.map((adj) => `${adj.time} ${(0, time_converter_1.serializeTypedTime)(adj.offset)}`);
57
+ return `z=${pairs.join(' ')}`;
58
+ }
59
+ // ============================================================================
60
+ // Time Description Serializer
61
+ // ============================================================================
62
+ /**
63
+ * Serialize a complete time description
64
+ *
65
+ * Outputs:
66
+ * - One t= line
67
+ * - Zero or more r= lines
68
+ * - Zero or one z= line
69
+ *
70
+ * @param td - TimeDescription object
71
+ * @returns Array of serialized lines (without line endings)
72
+ */
73
+ function serializeTimeDescription(td) {
74
+ const lines = [];
75
+ // t= timing (required)
76
+ lines.push(serializeTimingField(td.timing));
77
+ // r= repeat times (optional, multiple)
78
+ for (const repeat of td.repeats) {
79
+ lines.push(serializeRepeatField(repeat));
80
+ }
81
+ // z= timezone (optional, at most one)
82
+ if (td.timezone) {
83
+ lines.push(serializeTimezoneField(td.timezone));
84
+ }
85
+ return lines;
86
+ }
@@ -0,0 +1,318 @@
1
+ /**
2
+ * SDP attribute types (RFC 8866 Section 6)
3
+ *
4
+ * Defines types for SDP attributes, both property and value attributes.
5
+ * Includes 20+ predefined attributes with strong typing.
6
+ */
7
+ /**
8
+ * Property attribute - has name but no value
9
+ * Format: a=<attribute-name>
10
+ */
11
+ export interface PropertyAttribute {
12
+ readonly kind: 'property';
13
+ readonly name: string;
14
+ }
15
+ /**
16
+ * Value attribute - has both name and value
17
+ * Format: a=<attribute-name>:<attribute-value>
18
+ */
19
+ export interface ValueAttribute {
20
+ readonly kind: 'value';
21
+ readonly name: string;
22
+ readonly value: string;
23
+ }
24
+ /**
25
+ * Base attribute type - either property or value
26
+ */
27
+ export type Attribute = PropertyAttribute | ValueAttribute;
28
+ /**
29
+ * recvonly attribute (RFC 8866 Section 6.7)
30
+ * Indicates receive-only mode
31
+ */
32
+ export interface RecvonlyAttribute {
33
+ readonly kind: 'property';
34
+ readonly name: 'recvonly';
35
+ }
36
+ /**
37
+ * sendrecv attribute (RFC 8866 Section 6.7)
38
+ * Indicates send and receive mode (default)
39
+ */
40
+ export interface SendrecvAttribute {
41
+ readonly kind: 'property';
42
+ readonly name: 'sendrecv';
43
+ }
44
+ /**
45
+ * sendonly attribute (RFC 8866 Section 6.7)
46
+ * Indicates send-only mode
47
+ */
48
+ export interface SendonlyAttribute {
49
+ readonly kind: 'property';
50
+ readonly name: 'sendonly';
51
+ }
52
+ /**
53
+ * inactive attribute (RFC 8866 Section 6.7)
54
+ * Indicates neither send nor receive
55
+ */
56
+ export interface InactiveAttribute {
57
+ readonly kind: 'property';
58
+ readonly name: 'inactive';
59
+ }
60
+ /**
61
+ * cat attribute (RFC 8866 Section 6.1)
62
+ * Category of the session
63
+ */
64
+ export interface CatAttribute {
65
+ readonly kind: 'value';
66
+ readonly name: 'cat';
67
+ readonly value: string;
68
+ }
69
+ /**
70
+ * keywds attribute (RFC 8866 Section 6.2)
71
+ * Keywords for the session (comma-separated)
72
+ */
73
+ export interface KeywdsAttribute {
74
+ readonly kind: 'value';
75
+ readonly name: 'keywds';
76
+ readonly value: string;
77
+ }
78
+ /**
79
+ * tool attribute (RFC 8866 Section 6.3)
80
+ * Name and version of the tool used to create the description
81
+ */
82
+ export interface ToolAttribute {
83
+ readonly kind: 'value';
84
+ readonly name: 'tool';
85
+ readonly value: string;
86
+ }
87
+ /**
88
+ * ptime attribute (RFC 8866 Section 6.4)
89
+ * Packet time - duration of media in each packet (milliseconds)
90
+ *
91
+ * Syntax: ptime-value = non-zero-int-or-real
92
+ * Value can be an integer or decimal (e.g., "20" or "20.5")
93
+ */
94
+ export interface PtimeAttribute {
95
+ readonly kind: 'value';
96
+ readonly name: 'ptime';
97
+ readonly value: string;
98
+ }
99
+ /**
100
+ * maxptime attribute (RFC 8866 Section 6.5)
101
+ * Maximum packet time (milliseconds)
102
+ *
103
+ * Syntax: maxptime-value = non-zero-int-or-real
104
+ * Value can be an integer or decimal (e.g., "40" or "40.0")
105
+ */
106
+ export interface MaxptimeAttribute {
107
+ readonly kind: 'value';
108
+ readonly name: 'maxptime';
109
+ readonly value: string;
110
+ }
111
+ /**
112
+ * rtpmap attribute (RFC 8866 Section 6.6)
113
+ * Maps RTP payload type to encoding name
114
+ * Format: <payload-type> <encoding-name>/<clock-rate>[/<encoding-params>]
115
+ */
116
+ export interface RtpmapAttribute {
117
+ readonly kind: 'value';
118
+ readonly name: 'rtpmap';
119
+ readonly value: string;
120
+ }
121
+ /**
122
+ * Parsed rtpmap value
123
+ */
124
+ export interface RtpmapValue {
125
+ readonly payloadType: number;
126
+ readonly encodingName: string;
127
+ readonly clockRate: number;
128
+ readonly encodingParams?: number;
129
+ }
130
+ /**
131
+ * fmtp attribute (RFC 8866 Section 6.15)
132
+ * Format-specific parameters
133
+ * Format: <format> <format-specific-params>
134
+ */
135
+ export interface FmtpAttribute {
136
+ readonly kind: 'value';
137
+ readonly name: 'fmtp';
138
+ readonly value: string;
139
+ }
140
+ /**
141
+ * Parsed fmtp value
142
+ */
143
+ export interface FmtpValue {
144
+ readonly format: string;
145
+ readonly parameters: string;
146
+ }
147
+ /**
148
+ * orient attribute (RFC 8866 Section 6.8)
149
+ * Whiteboard or presentation tool orientation
150
+ * Values: "portrait", "landscape", "seascape"
151
+ */
152
+ export interface OrientAttribute {
153
+ readonly kind: 'value';
154
+ readonly name: 'orient';
155
+ readonly value: 'portrait' | 'landscape' | 'seascape';
156
+ }
157
+ /**
158
+ * type attribute (RFC 8866 Section 6.9)
159
+ * Type of conference (broadcast, meeting, moderated, test, H332)
160
+ */
161
+ export interface TypeAttribute {
162
+ readonly kind: 'value';
163
+ readonly name: 'type';
164
+ readonly value: 'broadcast' | 'meeting' | 'moderated' | 'test' | 'H332' | string;
165
+ }
166
+ /**
167
+ * charset attribute (RFC 8866 Section 6.10)
168
+ * Character set used in text fields
169
+ */
170
+ export interface CharsetAttribute {
171
+ readonly kind: 'value';
172
+ readonly name: 'charset';
173
+ readonly value: string;
174
+ }
175
+ /**
176
+ * sdplang attribute (RFC 8866 Section 6.11)
177
+ * Language tag for session description
178
+ */
179
+ export interface SdplangAttribute {
180
+ readonly kind: 'value';
181
+ readonly name: 'sdplang';
182
+ readonly value: string;
183
+ }
184
+ /**
185
+ * lang attribute (RFC 8866 Section 6.12)
186
+ * Language tag for media description
187
+ */
188
+ export interface LangAttribute {
189
+ readonly kind: 'value';
190
+ readonly name: 'lang';
191
+ readonly value: string;
192
+ }
193
+ /**
194
+ * framerate attribute (RFC 8866 Section 6.13)
195
+ * Video frame rate (frames per second)
196
+ */
197
+ export interface FramerateAttribute {
198
+ readonly kind: 'value';
199
+ readonly name: 'framerate';
200
+ readonly value: string;
201
+ }
202
+ /**
203
+ * quality attribute (RFC 8866 Section 6.14)
204
+ * Suggested quality for video encoding (0-10)
205
+ */
206
+ export interface QualityAttribute {
207
+ readonly kind: 'value';
208
+ readonly name: 'quality';
209
+ readonly value: string;
210
+ }
211
+ /**
212
+ * All predefined attributes from RFC 8866
213
+ */
214
+ export type PredefinedAttribute = RecvonlyAttribute | SendrecvAttribute | SendonlyAttribute | InactiveAttribute | CatAttribute | KeywdsAttribute | ToolAttribute | PtimeAttribute | MaxptimeAttribute | RtpmapAttribute | FmtpAttribute | OrientAttribute | TypeAttribute | CharsetAttribute | SdplangAttribute | LangAttribute | FramerateAttribute | QualityAttribute;
215
+ /**
216
+ * Custom/extension attribute - not predefined in RFC 8866
217
+ */
218
+ export type CustomAttribute = PropertyAttribute | ValueAttribute;
219
+ /**
220
+ * Create a property attribute
221
+ */
222
+ export declare function createPropertyAttribute(name: string): PropertyAttribute;
223
+ /**
224
+ * Create a value attribute
225
+ */
226
+ export declare function createValueAttribute(name: string, value: string): ValueAttribute;
227
+ /**
228
+ * Create recvonly attribute
229
+ */
230
+ export declare function createRecvonly(): RecvonlyAttribute;
231
+ /**
232
+ * Create sendrecv attribute
233
+ */
234
+ export declare function createSendrecv(): SendrecvAttribute;
235
+ /**
236
+ * Create sendonly attribute
237
+ */
238
+ export declare function createSendonly(): SendonlyAttribute;
239
+ /**
240
+ * Create inactive attribute
241
+ */
242
+ export declare function createInactive(): InactiveAttribute;
243
+ /**
244
+ * Create rtpmap attribute
245
+ */
246
+ export declare function createRtpmap(payloadType: number, encodingName: string, clockRate: number, encodingParams?: number): RtpmapAttribute;
247
+ /**
248
+ * Create fmtp attribute
249
+ */
250
+ export declare function createFmtp(format: string, parameters: string): FmtpAttribute;
251
+ /**
252
+ * Create ptime attribute
253
+ */
254
+ export declare function createPtime(milliseconds: number): PtimeAttribute;
255
+ /**
256
+ * Create maxptime attribute
257
+ */
258
+ export declare function createMaxptime(milliseconds: number): MaxptimeAttribute;
259
+ /**
260
+ * Attribute usage level - where an attribute can appear
261
+ *
262
+ * - 'session': Only valid at session level
263
+ * - 'media': Only valid at media level
264
+ * - 'both': Valid at either session or media level
265
+ */
266
+ export type AttributeUsageLevel = 'session' | 'media' | 'both';
267
+ /**
268
+ * Map of predefined attribute names to their allowed usage levels
269
+ *
270
+ * Per RFC 8866 Section 6:
271
+ * - Session only: cat (6.1), keywds (6.2), tool (6.3), type (6.9), charset (6.10)
272
+ * - Media only: ptime (6.4), maxptime (6.5), rtpmap (6.6), orient (6.8),
273
+ * framerate (6.13), quality (6.14), fmtp (6.15)
274
+ * - Both levels: recvonly, sendrecv, sendonly, inactive (6.7), sdplang (6.11), lang (6.12)
275
+ */
276
+ export declare const ATTRIBUTE_USAGE_LEVELS: Readonly<Record<string, AttributeUsageLevel>>;
277
+ /**
278
+ * Get the usage level for an attribute
279
+ *
280
+ * @param attributeName - Name of the attribute
281
+ * @returns Usage level, or undefined for unknown/extension attributes
282
+ */
283
+ export declare function getAttributeUsageLevel(attributeName: string): AttributeUsageLevel | undefined;
284
+ /**
285
+ * Check if an attribute is valid at session level
286
+ *
287
+ * @param attributeName - Name of the attribute
288
+ * @returns true if valid at session level, false if media-only, undefined if unknown
289
+ */
290
+ export declare function isValidAtSessionLevel(attributeName: string): boolean | undefined;
291
+ /**
292
+ * Check if an attribute is valid at media level
293
+ *
294
+ * @param attributeName - Name of the attribute
295
+ * @returns true if valid at media level, false if session-only, undefined if unknown
296
+ */
297
+ export declare function isValidAtMediaLevel(attributeName: string): boolean | undefined;
298
+ /**
299
+ * Names of attributes that are only valid at session level
300
+ */
301
+ export declare const SESSION_ONLY_ATTRIBUTES: readonly string[];
302
+ /**
303
+ * Names of attributes that are only valid at media level
304
+ */
305
+ export declare const MEDIA_ONLY_ATTRIBUTES: readonly string[];
306
+ /**
307
+ * Check if attribute is a property attribute
308
+ */
309
+ export declare function isPropertyAttribute(attr: Attribute): attr is PropertyAttribute;
310
+ /**
311
+ * Check if attribute is a value attribute
312
+ */
313
+ export declare function isValueAttribute(attr: Attribute): attr is ValueAttribute;
314
+ /**
315
+ * Check if attribute is a direction attribute
316
+ */
317
+ export declare function isDirectionAttribute(attr: Attribute): attr is RecvonlyAttribute | SendrecvAttribute | SendonlyAttribute | InactiveAttribute;
318
+ //# sourceMappingURL=attributes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attributes.d.ts","sourceRoot":"","sources":["../../../src/types/attributes.ts"],"names":[],"mappings":"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"}