@pnosolutions/ipp-core 0.1.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.
- package/LICENSE +15 -0
- package/dist/index.d.ts +155 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +377 -0
- package/dist/index.js.map +1 -0
- package/package.json +40 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PNO Solutions Limited
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
11
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
12
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
13
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
14
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
PERFORMANCE OF THIS SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
interface PrintConfig {
|
|
3
|
+
defaultPrinter?: string;
|
|
4
|
+
printers?: Record<string, PrinterEntry>;
|
|
5
|
+
timeout?: number;
|
|
6
|
+
verbose: boolean;
|
|
7
|
+
}
|
|
8
|
+
interface PrinterEntry {
|
|
9
|
+
uri: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
model?: string;
|
|
12
|
+
}
|
|
13
|
+
declare const IppVersion: {
|
|
14
|
+
V1_1: readonly [1, 1];
|
|
15
|
+
V2_0: readonly [2, 0];
|
|
16
|
+
};
|
|
17
|
+
declare const IppOperation: {
|
|
18
|
+
readonly PrintJob: 2;
|
|
19
|
+
readonly ValidateJob: 4;
|
|
20
|
+
readonly CreateJob: 5;
|
|
21
|
+
readonly SendDocument: 6;
|
|
22
|
+
readonly CancelJob: 8;
|
|
23
|
+
readonly GetJobAttributes: 9;
|
|
24
|
+
readonly GetJobs: 10;
|
|
25
|
+
readonly GetPrinterAttributes: 11;
|
|
26
|
+
readonly PausePrinter: 16;
|
|
27
|
+
readonly ResumePrinter: 17;
|
|
28
|
+
readonly IdentifyPrinter: 60;
|
|
29
|
+
};
|
|
30
|
+
type IppOperationId = (typeof IppOperation)[keyof typeof IppOperation];
|
|
31
|
+
declare const IppStatusCode: {
|
|
32
|
+
readonly SuccessfulOk: 0;
|
|
33
|
+
readonly SuccessfulOkIgnoredOrSubstituted: 1;
|
|
34
|
+
readonly SuccessfulOkConflicting: 2;
|
|
35
|
+
readonly ClientErrorBadRequest: 1024;
|
|
36
|
+
readonly ClientErrorForbidden: 1025;
|
|
37
|
+
readonly ClientErrorNotAuthenticated: 1026;
|
|
38
|
+
readonly ClientErrorNotAuthorized: 1027;
|
|
39
|
+
readonly ClientErrorNotPossible: 1028;
|
|
40
|
+
readonly ClientErrorTimeout: 1029;
|
|
41
|
+
readonly ClientErrorNotFound: 1030;
|
|
42
|
+
readonly ClientErrorGone: 1031;
|
|
43
|
+
readonly ClientErrorDocumentFormatNotSupported: 1034;
|
|
44
|
+
readonly ClientErrorAttributesOrValuesNotSupported: 1035;
|
|
45
|
+
readonly ServerErrorInternalError: 1280;
|
|
46
|
+
readonly ServerErrorOperationNotSupported: 1281;
|
|
47
|
+
readonly ServerErrorServiceUnavailable: 1282;
|
|
48
|
+
readonly ServerErrorVersionNotSupported: 1283;
|
|
49
|
+
readonly ServerErrorDeviceError: 1284;
|
|
50
|
+
readonly ServerErrorTemporaryError: 1285;
|
|
51
|
+
readonly ServerErrorBusy: 1287;
|
|
52
|
+
};
|
|
53
|
+
declare const IppTag: {
|
|
54
|
+
readonly OperationAttributes: 1;
|
|
55
|
+
readonly JobAttributes: 2;
|
|
56
|
+
readonly EndOfAttributes: 3;
|
|
57
|
+
readonly PrinterAttributes: 4;
|
|
58
|
+
readonly UnsupportedAttributes: 5;
|
|
59
|
+
readonly Unsupported: 16;
|
|
60
|
+
readonly Unknown: 18;
|
|
61
|
+
readonly NoValue: 19;
|
|
62
|
+
readonly Integer: 33;
|
|
63
|
+
readonly Boolean: 34;
|
|
64
|
+
readonly Enum: 35;
|
|
65
|
+
readonly OctetString: 48;
|
|
66
|
+
readonly DateTime: 49;
|
|
67
|
+
readonly Resolution: 50;
|
|
68
|
+
readonly RangeOfInteger: 51;
|
|
69
|
+
readonly BegCollection: 52;
|
|
70
|
+
readonly TextWithLanguage: 53;
|
|
71
|
+
readonly NameWithLanguage: 54;
|
|
72
|
+
readonly EndCollection: 55;
|
|
73
|
+
readonly TextWithoutLanguage: 65;
|
|
74
|
+
readonly NameWithoutLanguage: 66;
|
|
75
|
+
readonly Keyword: 68;
|
|
76
|
+
readonly Uri: 69;
|
|
77
|
+
readonly UriScheme: 70;
|
|
78
|
+
readonly Charset: 71;
|
|
79
|
+
readonly NaturalLanguage: 72;
|
|
80
|
+
readonly MimeMediaType: 73;
|
|
81
|
+
readonly MemberAttrName: 74;
|
|
82
|
+
};
|
|
83
|
+
type IppTagValue = (typeof IppTag)[keyof typeof IppTag];
|
|
84
|
+
interface IppAttribute {
|
|
85
|
+
tag: number;
|
|
86
|
+
name: string;
|
|
87
|
+
value: IppAttributeValue;
|
|
88
|
+
}
|
|
89
|
+
type IppAttributeValue = string | number | boolean | Uint8Array | IppResolution | IppCollection | IppAttributeValue[];
|
|
90
|
+
interface IppCollection {
|
|
91
|
+
[memberName: string]: IppAttributeValue;
|
|
92
|
+
}
|
|
93
|
+
/** RFC 8011 §5.1.16 'resolution' value. */
|
|
94
|
+
interface IppResolution {
|
|
95
|
+
/** Cross-feed direction resolution. */
|
|
96
|
+
x: number;
|
|
97
|
+
/** Feed direction resolution. */
|
|
98
|
+
y: number;
|
|
99
|
+
/** Units: 3 = dots per inch, 4 = dots per centimeter. */
|
|
100
|
+
units: number;
|
|
101
|
+
}
|
|
102
|
+
declare const IppResolutionUnit: {
|
|
103
|
+
readonly DotsPerInch: 3;
|
|
104
|
+
readonly DotsPerCentimeter: 4;
|
|
105
|
+
};
|
|
106
|
+
interface IppAttributeGroup {
|
|
107
|
+
tag: number;
|
|
108
|
+
attributes: IppAttribute[];
|
|
109
|
+
}
|
|
110
|
+
interface IppRequest {
|
|
111
|
+
version?: readonly [number, number];
|
|
112
|
+
operation: IppOperationId;
|
|
113
|
+
requestId?: number;
|
|
114
|
+
groups: IppAttributeGroup[];
|
|
115
|
+
data?: Uint8Array;
|
|
116
|
+
}
|
|
117
|
+
interface IppResponse {
|
|
118
|
+
version: [number, number];
|
|
119
|
+
statusCode: number;
|
|
120
|
+
requestId: number;
|
|
121
|
+
groups: IppAttributeGroup[];
|
|
122
|
+
data?: Uint8Array;
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/collection.d.ts
|
|
126
|
+
declare class CollectionParser {
|
|
127
|
+
private readonly src;
|
|
128
|
+
private pos;
|
|
129
|
+
constructor(src: string);
|
|
130
|
+
parseObject(): IppCollection;
|
|
131
|
+
private parseValue;
|
|
132
|
+
private readToken;
|
|
133
|
+
private skipWs;
|
|
134
|
+
}
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region src/encoding.d.ts
|
|
137
|
+
/**
|
|
138
|
+
* Encode an IPP request into a binary buffer
|
|
139
|
+
*/
|
|
140
|
+
declare function encodeIppRequest(request: IppRequest): Uint8Array<ArrayBuffer>;
|
|
141
|
+
/**
|
|
142
|
+
* Decode an IPP response from a binary buffer
|
|
143
|
+
*/
|
|
144
|
+
declare function decodeIppResponse(data: Uint8Array): IppResponse;
|
|
145
|
+
/**
|
|
146
|
+
* Helper to get a flat map of attribute name -> value from response groups
|
|
147
|
+
*/
|
|
148
|
+
declare function getAttributes(response: IppResponse, groupTag?: number): Record<string, IppAttributeValue>;
|
|
149
|
+
/**
|
|
150
|
+
* Helper to get a single attribute value
|
|
151
|
+
*/
|
|
152
|
+
declare function getAttribute(response: IppResponse, name: string, groupTag?: number): IppAttributeValue | undefined;
|
|
153
|
+
//#endregion
|
|
154
|
+
export { CollectionParser, IppAttribute, IppAttributeGroup, IppAttributeValue, IppCollection, IppOperation, IppOperationId, IppRequest, IppResolution, IppResolutionUnit, IppResponse, IppStatusCode, IppTag, IppTagValue, IppVersion, PrintConfig, PrinterEntry, decodeIppResponse, encodeIppRequest, getAttribute, getAttributes };
|
|
155
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/collection.ts","../src/encoding.ts"],"mappings":";UAIiB,WAAA;EACf,cAAA;EACA,QAAA,GAAW,MAAM,SAAS,YAAA;EAC1B,OAAA;EACA,OAAA;AAAA;AAAA,UAGe,YAAA;EACf,GAAA;EACA,IAAA;EACA,KAAA;AAAA;AAAA,cAKW,UAAA;EAGZ,IAAA;EAAA,IAAA;AAAA;AAAA,cAEY,YAAA;EAAA;;;;;;;;;;;;KAcD,cAAA,WAAyB,YAAA,eAA2B,YAAY;AAAA,cAE/D,aAAA;EAAA;;;;;;;;;;;;;;;;;;;;;cAuBA,MAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAwCD,WAAA,WAAsB,MAAA,eAAqB,MAAM;AAAA,UAI5C,YAAA;EACf,GAAA;EACA,IAAA;EACA,KAAA,EAAO,iBAAiB;AAAA;AAAA,KAGd,iBAAA,+BAIR,UAAA,GACA,aAAA,GACA,aAAA,GACA,iBAAA;AAAA,UAEa,aAAA;EAAA,CACd,UAAA,WAAqB,iBAAiB;AAAA;;UAIxB,aAAA;;EAEf,CAAA;;EAEA,CAAA;;EAEA,KAAA;AAAA;AAAA,cAGW,iBAAA;EAAA,SAGH,WAAA;EAAA,SAAA,iBAAA;AAAA;AAAA,UAEO,iBAAA;EACf,GAAA;EACA,UAAA,EAAY,YAAY;AAAA;AAAA,UAGT,UAAA;EACf,OAAA;EACA,SAAA,EAAW,cAAA;EACX,SAAA;EACA,MAAA,EAAQ,iBAAA;EACR,IAAA,GAAO,UAAA;AAAA;AAAA,UAGQ,WAAA;EACf,OAAA;EACA,UAAA;EACA,SAAA;EACA,MAAA,EAAQ,iBAAA;EACR,IAAA,GAAO,UAAU;AAAA;;;cC7JN,gBAAA;EAAA,iBAGkB,GAAA;EAAA,QAFrB,GAAA;cAEqB,GAAA;EAE7B,WAAA,CAAA,GAAe,aAAa;EAAA,QAqBpB,UAAA;EAAA,QAKA,SAAA;EAAA,QAQA,MAAA;AAAA;;;ADrCV;;;AAAA,iBEiBgB,gBAAA,CAAiB,OAAA,EAAS,UAAA,GAAa,UAAA,CAAW,WAAA;;;;iBAyNlD,iBAAA,CAAkB,IAAA,EAAM,UAAA,GAAa,WAAW;;;;iBAyHhD,aAAA,CACd,QAAA,EAAU,WAAA,EACV,QAAA,YACC,MAAA,SAAe,iBAAA;AF/VlB;;;AAAA,iBEkXgB,YAAA,CACd,QAAA,EAAU,WAAA,EACV,IAAA,UACA,QAAA,YACC,iBAAiB"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
//#region src/collection.ts
|
|
2
|
+
var CollectionParser = class {
|
|
3
|
+
src;
|
|
4
|
+
pos = 0;
|
|
5
|
+
constructor(src) {
|
|
6
|
+
this.src = src;
|
|
7
|
+
}
|
|
8
|
+
parseObject() {
|
|
9
|
+
const obj = {};
|
|
10
|
+
this.skipWs();
|
|
11
|
+
if (this.src[this.pos] !== "{") return obj;
|
|
12
|
+
this.pos++;
|
|
13
|
+
this.skipWs();
|
|
14
|
+
while (this.pos < this.src.length && this.src[this.pos] !== "}") {
|
|
15
|
+
const key = this.readToken();
|
|
16
|
+
if (!key) break;
|
|
17
|
+
this.skipWs();
|
|
18
|
+
if (this.src[this.pos] === "=") this.pos++;
|
|
19
|
+
this.skipWs();
|
|
20
|
+
obj[key] = this.parseValue();
|
|
21
|
+
this.skipWs();
|
|
22
|
+
}
|
|
23
|
+
if (this.src[this.pos] === "}") this.pos++;
|
|
24
|
+
return obj;
|
|
25
|
+
}
|
|
26
|
+
parseValue() {
|
|
27
|
+
if (this.src[this.pos] === "{") return this.parseObject();
|
|
28
|
+
return coerce(this.readToken());
|
|
29
|
+
}
|
|
30
|
+
readToken() {
|
|
31
|
+
const start = this.pos;
|
|
32
|
+
while (this.pos < this.src.length && !/[\s={}]/.test(this.src[this.pos])) this.pos++;
|
|
33
|
+
return this.src.slice(start, this.pos);
|
|
34
|
+
}
|
|
35
|
+
skipWs() {
|
|
36
|
+
while (this.pos < this.src.length && /\s/.test(this.src[this.pos])) this.pos++;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
function coerce(raw) {
|
|
40
|
+
if (raw === "true") return true;
|
|
41
|
+
if (raw === "false") return false;
|
|
42
|
+
if (/^-?\d+$/.test(raw)) return parseInt(raw, 10);
|
|
43
|
+
if (/^-?\d+\.\d+$/.test(raw)) return parseFloat(raw);
|
|
44
|
+
return raw;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/types.ts
|
|
48
|
+
const IppVersion = {
|
|
49
|
+
V1_1: [1, 1],
|
|
50
|
+
V2_0: [2, 0]
|
|
51
|
+
};
|
|
52
|
+
const IppOperation = {
|
|
53
|
+
PrintJob: 2,
|
|
54
|
+
ValidateJob: 4,
|
|
55
|
+
CreateJob: 5,
|
|
56
|
+
SendDocument: 6,
|
|
57
|
+
CancelJob: 8,
|
|
58
|
+
GetJobAttributes: 9,
|
|
59
|
+
GetJobs: 10,
|
|
60
|
+
GetPrinterAttributes: 11,
|
|
61
|
+
PausePrinter: 16,
|
|
62
|
+
ResumePrinter: 17,
|
|
63
|
+
IdentifyPrinter: 60
|
|
64
|
+
};
|
|
65
|
+
const IppStatusCode = {
|
|
66
|
+
SuccessfulOk: 0,
|
|
67
|
+
SuccessfulOkIgnoredOrSubstituted: 1,
|
|
68
|
+
SuccessfulOkConflicting: 2,
|
|
69
|
+
ClientErrorBadRequest: 1024,
|
|
70
|
+
ClientErrorForbidden: 1025,
|
|
71
|
+
ClientErrorNotAuthenticated: 1026,
|
|
72
|
+
ClientErrorNotAuthorized: 1027,
|
|
73
|
+
ClientErrorNotPossible: 1028,
|
|
74
|
+
ClientErrorTimeout: 1029,
|
|
75
|
+
ClientErrorNotFound: 1030,
|
|
76
|
+
ClientErrorGone: 1031,
|
|
77
|
+
ClientErrorDocumentFormatNotSupported: 1034,
|
|
78
|
+
ClientErrorAttributesOrValuesNotSupported: 1035,
|
|
79
|
+
ServerErrorInternalError: 1280,
|
|
80
|
+
ServerErrorOperationNotSupported: 1281,
|
|
81
|
+
ServerErrorServiceUnavailable: 1282,
|
|
82
|
+
ServerErrorVersionNotSupported: 1283,
|
|
83
|
+
ServerErrorDeviceError: 1284,
|
|
84
|
+
ServerErrorTemporaryError: 1285,
|
|
85
|
+
ServerErrorBusy: 1287
|
|
86
|
+
};
|
|
87
|
+
const IppTag = {
|
|
88
|
+
OperationAttributes: 1,
|
|
89
|
+
JobAttributes: 2,
|
|
90
|
+
EndOfAttributes: 3,
|
|
91
|
+
PrinterAttributes: 4,
|
|
92
|
+
UnsupportedAttributes: 5,
|
|
93
|
+
Unsupported: 16,
|
|
94
|
+
Unknown: 18,
|
|
95
|
+
NoValue: 19,
|
|
96
|
+
Integer: 33,
|
|
97
|
+
Boolean: 34,
|
|
98
|
+
Enum: 35,
|
|
99
|
+
OctetString: 48,
|
|
100
|
+
DateTime: 49,
|
|
101
|
+
Resolution: 50,
|
|
102
|
+
RangeOfInteger: 51,
|
|
103
|
+
BegCollection: 52,
|
|
104
|
+
TextWithLanguage: 53,
|
|
105
|
+
NameWithLanguage: 54,
|
|
106
|
+
EndCollection: 55,
|
|
107
|
+
TextWithoutLanguage: 65,
|
|
108
|
+
NameWithoutLanguage: 66,
|
|
109
|
+
Keyword: 68,
|
|
110
|
+
Uri: 69,
|
|
111
|
+
UriScheme: 70,
|
|
112
|
+
Charset: 71,
|
|
113
|
+
NaturalLanguage: 72,
|
|
114
|
+
MimeMediaType: 73,
|
|
115
|
+
MemberAttrName: 74
|
|
116
|
+
};
|
|
117
|
+
const IppResolutionUnit = {
|
|
118
|
+
DotsPerInch: 3,
|
|
119
|
+
DotsPerCentimeter: 4
|
|
120
|
+
};
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region src/utils.ts
|
|
123
|
+
function viewOf(arr) {
|
|
124
|
+
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
125
|
+
}
|
|
126
|
+
function concat(chunks) {
|
|
127
|
+
let total = 0;
|
|
128
|
+
for (const c of chunks) total += c.length;
|
|
129
|
+
const result = new Uint8Array(total);
|
|
130
|
+
let offset = 0;
|
|
131
|
+
for (const c of chunks) {
|
|
132
|
+
result.set(c, offset);
|
|
133
|
+
offset += c.length;
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/encoding.ts
|
|
139
|
+
const textEncoder = new TextEncoder();
|
|
140
|
+
const textDecoder = new TextDecoder("utf-8");
|
|
141
|
+
/**
|
|
142
|
+
* Encode an IPP request into a binary buffer
|
|
143
|
+
*/
|
|
144
|
+
function encodeIppRequest(request) {
|
|
145
|
+
const version = request.version ?? IppVersion.V2_0;
|
|
146
|
+
const requestId = request.requestId ?? 1;
|
|
147
|
+
const chunks = [];
|
|
148
|
+
const header = new Uint8Array(8);
|
|
149
|
+
const headerView = viewOf(header);
|
|
150
|
+
headerView.setUint8(0, version[0]);
|
|
151
|
+
headerView.setUint8(1, version[1]);
|
|
152
|
+
headerView.setUint16(2, request.operation, false);
|
|
153
|
+
headerView.setUint32(4, requestId, false);
|
|
154
|
+
chunks.push(header);
|
|
155
|
+
for (const group of request.groups) {
|
|
156
|
+
chunks.push(new Uint8Array([group.tag]));
|
|
157
|
+
for (const attr of group.attributes) chunks.push(encodeAttribute(attr));
|
|
158
|
+
}
|
|
159
|
+
chunks.push(new Uint8Array([IppTag.EndOfAttributes]));
|
|
160
|
+
if (request.data) chunks.push(request.data);
|
|
161
|
+
return concat(chunks);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Encode a single IPP attribute
|
|
165
|
+
*/
|
|
166
|
+
function encodeAttribute(attr) {
|
|
167
|
+
if (Array.isArray(attr.value)) {
|
|
168
|
+
const chunks = [];
|
|
169
|
+
for (let i = 0; i < attr.value.length; i++) {
|
|
170
|
+
const singleAttr = {
|
|
171
|
+
tag: attr.tag,
|
|
172
|
+
name: i === 0 ? attr.name : "",
|
|
173
|
+
value: attr.value[i]
|
|
174
|
+
};
|
|
175
|
+
chunks.push(encodeSingleAttribute(singleAttr));
|
|
176
|
+
}
|
|
177
|
+
return concat(chunks);
|
|
178
|
+
}
|
|
179
|
+
return encodeSingleAttribute(attr);
|
|
180
|
+
}
|
|
181
|
+
function encodeSingleAttribute(attr) {
|
|
182
|
+
const nameBytes = textEncoder.encode(attr.name);
|
|
183
|
+
const valueBytes = encodeValue(attr.tag, attr.value);
|
|
184
|
+
const buf = new Uint8Array(3 + nameBytes.length + 2 + valueBytes.length);
|
|
185
|
+
const view = viewOf(buf);
|
|
186
|
+
let offset = 0;
|
|
187
|
+
view.setUint8(offset, attr.tag);
|
|
188
|
+
offset += 1;
|
|
189
|
+
view.setUint16(offset, nameBytes.length, false);
|
|
190
|
+
offset += 2;
|
|
191
|
+
buf.set(nameBytes, offset);
|
|
192
|
+
offset += nameBytes.length;
|
|
193
|
+
view.setUint16(offset, valueBytes.length, false);
|
|
194
|
+
offset += 2;
|
|
195
|
+
buf.set(valueBytes, offset);
|
|
196
|
+
return buf;
|
|
197
|
+
}
|
|
198
|
+
function encodeValue(tag, value) {
|
|
199
|
+
if (Array.isArray(value)) return encodeValue(tag, value[0]);
|
|
200
|
+
switch (tag) {
|
|
201
|
+
case IppTag.Integer:
|
|
202
|
+
case IppTag.Enum: {
|
|
203
|
+
const buf = new Uint8Array(4);
|
|
204
|
+
viewOf(buf).setInt32(0, value, false);
|
|
205
|
+
return buf;
|
|
206
|
+
}
|
|
207
|
+
case IppTag.Boolean: return new Uint8Array([value ? 1 : 0]);
|
|
208
|
+
case IppTag.RangeOfInteger: {
|
|
209
|
+
if (value instanceof Uint8Array) return value;
|
|
210
|
+
const buf = new Uint8Array(8);
|
|
211
|
+
const view = viewOf(buf);
|
|
212
|
+
view.setInt32(0, value, false);
|
|
213
|
+
view.setInt32(4, value, false);
|
|
214
|
+
return buf;
|
|
215
|
+
}
|
|
216
|
+
case IppTag.Resolution: {
|
|
217
|
+
if (value instanceof Uint8Array) return value;
|
|
218
|
+
const buf = new Uint8Array(9);
|
|
219
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.x === "number" && typeof value.y === "number" && typeof value.units === "number") {
|
|
220
|
+
const view = viewOf(buf);
|
|
221
|
+
view.setInt32(0, value.x, false);
|
|
222
|
+
view.setInt32(4, value.y, false);
|
|
223
|
+
view.setInt8(8, value.units);
|
|
224
|
+
}
|
|
225
|
+
return buf;
|
|
226
|
+
}
|
|
227
|
+
case IppTag.DateTime:
|
|
228
|
+
if (value instanceof Uint8Array) return value;
|
|
229
|
+
return new Uint8Array(11);
|
|
230
|
+
case IppTag.NoValue:
|
|
231
|
+
case IppTag.Unknown:
|
|
232
|
+
case IppTag.Unsupported: return new Uint8Array(0);
|
|
233
|
+
default:
|
|
234
|
+
if (value instanceof Uint8Array) return value;
|
|
235
|
+
return textEncoder.encode(String(value));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function readNameAndValue(data, view, cursor) {
|
|
239
|
+
const nameLength = view.getUint16(cursor.pos, false);
|
|
240
|
+
cursor.pos += 2;
|
|
241
|
+
const name = textDecoder.decode(data.subarray(cursor.pos, cursor.pos + nameLength));
|
|
242
|
+
cursor.pos += nameLength;
|
|
243
|
+
const valueLength = view.getUint16(cursor.pos, false);
|
|
244
|
+
cursor.pos += 2;
|
|
245
|
+
const rawValue = data.subarray(cursor.pos, cursor.pos + valueLength);
|
|
246
|
+
cursor.pos += valueLength;
|
|
247
|
+
return {
|
|
248
|
+
name,
|
|
249
|
+
nameLength,
|
|
250
|
+
rawValue
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Parse a collection body up to and including the EndCollection marker.
|
|
255
|
+
* Cursor must be positioned immediately after a BegCollection record.
|
|
256
|
+
*/
|
|
257
|
+
function parseCollection(data, view, cursor) {
|
|
258
|
+
const collection = {};
|
|
259
|
+
let currentMember = null;
|
|
260
|
+
while (cursor.pos < data.length) {
|
|
261
|
+
const tag = view.getUint8(cursor.pos);
|
|
262
|
+
cursor.pos += 1;
|
|
263
|
+
const { nameLength, rawValue } = readNameAndValue(data, view, cursor);
|
|
264
|
+
if (tag === IppTag.EndCollection) return collection;
|
|
265
|
+
if (tag === IppTag.MemberAttrName) {
|
|
266
|
+
currentMember = textDecoder.decode(rawValue);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (currentMember === null) continue;
|
|
270
|
+
const value = tag === IppTag.BegCollection ? parseCollection(data, view, cursor) : decodeValue(tag, rawValue);
|
|
271
|
+
if (nameLength === 0 && currentMember in collection) {
|
|
272
|
+
const existing = collection[currentMember];
|
|
273
|
+
if (Array.isArray(existing)) existing.push(value);
|
|
274
|
+
else collection[currentMember] = [existing, value];
|
|
275
|
+
} else collection[currentMember] = value;
|
|
276
|
+
}
|
|
277
|
+
return collection;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Decode an IPP response from a binary buffer
|
|
281
|
+
*/
|
|
282
|
+
function decodeIppResponse(data) {
|
|
283
|
+
const view = viewOf(data);
|
|
284
|
+
const cursor = { pos: 0 };
|
|
285
|
+
const versionMajor = view.getUint8(cursor.pos);
|
|
286
|
+
cursor.pos += 1;
|
|
287
|
+
const versionMinor = view.getUint8(cursor.pos);
|
|
288
|
+
cursor.pos += 1;
|
|
289
|
+
const statusCode = view.getUint16(cursor.pos, false);
|
|
290
|
+
cursor.pos += 2;
|
|
291
|
+
const requestId = view.getUint32(cursor.pos, false);
|
|
292
|
+
cursor.pos += 4;
|
|
293
|
+
const groups = [];
|
|
294
|
+
let currentGroup = null;
|
|
295
|
+
while (cursor.pos < data.length) {
|
|
296
|
+
const tag = view.getUint8(cursor.pos);
|
|
297
|
+
cursor.pos += 1;
|
|
298
|
+
if (tag === IppTag.EndOfAttributes) break;
|
|
299
|
+
if (isDelimiterTag(tag)) {
|
|
300
|
+
currentGroup = {
|
|
301
|
+
tag,
|
|
302
|
+
attributes: []
|
|
303
|
+
};
|
|
304
|
+
groups.push(currentGroup);
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (!currentGroup) continue;
|
|
308
|
+
const { name, nameLength, rawValue } = readNameAndValue(data, view, cursor);
|
|
309
|
+
const value = tag === IppTag.BegCollection ? parseCollection(data, view, cursor) : decodeValue(tag, rawValue);
|
|
310
|
+
if (nameLength === 0 && currentGroup.attributes.length > 0) {
|
|
311
|
+
const prevAttr = currentGroup.attributes[currentGroup.attributes.length - 1];
|
|
312
|
+
if (Array.isArray(prevAttr.value)) prevAttr.value.push(value);
|
|
313
|
+
else prevAttr.value = [prevAttr.value, value];
|
|
314
|
+
} else currentGroup.attributes.push({
|
|
315
|
+
tag,
|
|
316
|
+
name,
|
|
317
|
+
value
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
const remainingData = cursor.pos < data.length ? data.slice(cursor.pos) : void 0;
|
|
321
|
+
return {
|
|
322
|
+
version: [versionMajor, versionMinor],
|
|
323
|
+
statusCode,
|
|
324
|
+
requestId,
|
|
325
|
+
groups,
|
|
326
|
+
data: remainingData
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function isDelimiterTag(tag) {
|
|
330
|
+
return tag >= 0 && tag <= 15;
|
|
331
|
+
}
|
|
332
|
+
function decodeValue(tag, raw) {
|
|
333
|
+
switch (tag) {
|
|
334
|
+
case IppTag.Integer:
|
|
335
|
+
case IppTag.Enum: return viewOf(raw).getInt32(0, false);
|
|
336
|
+
case IppTag.Boolean: return viewOf(raw).getUint8(0) !== 0;
|
|
337
|
+
case IppTag.RangeOfInteger: return raw.slice();
|
|
338
|
+
case IppTag.Resolution: {
|
|
339
|
+
const view = viewOf(raw);
|
|
340
|
+
return {
|
|
341
|
+
x: view.getInt32(0, false),
|
|
342
|
+
y: view.getInt32(4, false),
|
|
343
|
+
units: view.getInt8(8)
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
case IppTag.DateTime: return raw.slice();
|
|
347
|
+
case IppTag.NoValue:
|
|
348
|
+
case IppTag.Unknown:
|
|
349
|
+
case IppTag.Unsupported: return "";
|
|
350
|
+
case IppTag.OctetString: return raw.slice();
|
|
351
|
+
default: return textDecoder.decode(raw);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Helper to get a flat map of attribute name -> value from response groups
|
|
356
|
+
*/
|
|
357
|
+
function getAttributes(response, groupTag) {
|
|
358
|
+
const result = {};
|
|
359
|
+
for (const group of response.groups) {
|
|
360
|
+
if (groupTag !== void 0 && group.tag !== groupTag) continue;
|
|
361
|
+
for (const attr of group.attributes) if (attr.name) result[attr.name] = attr.value;
|
|
362
|
+
}
|
|
363
|
+
return result;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Helper to get a single attribute value
|
|
367
|
+
*/
|
|
368
|
+
function getAttribute(response, name, groupTag) {
|
|
369
|
+
for (const group of response.groups) {
|
|
370
|
+
if (groupTag !== void 0 && group.tag !== groupTag) continue;
|
|
371
|
+
for (const attr of group.attributes) if (attr.name === name) return attr.value;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
//#endregion
|
|
375
|
+
export { CollectionParser, IppOperation, IppResolutionUnit, IppStatusCode, IppTag, IppVersion, decodeIppResponse, encodeIppRequest, getAttribute, getAttributes };
|
|
376
|
+
|
|
377
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/collection.ts","../src/types.ts","../src/utils.ts","../src/encoding.ts"],"sourcesContent":["import { IppAttributeValue, IppCollection } from './types';\n\nexport class CollectionParser {\n private pos = 0;\n\n constructor(private readonly src: string) {}\n\n parseObject(): IppCollection {\n const obj: IppCollection = {};\n this.skipWs();\n if (this.src[this.pos] !== '{') return obj;\n this.pos++;\n this.skipWs();\n\n while (this.pos < this.src.length && this.src[this.pos] !== '}') {\n const key = this.readToken();\n if (!key) break;\n this.skipWs();\n if (this.src[this.pos] === '=') this.pos++;\n this.skipWs();\n obj[key] = this.parseValue();\n this.skipWs();\n }\n\n if (this.src[this.pos] === '}') this.pos++;\n return obj;\n }\n\n private parseValue(): IppAttributeValue {\n if (this.src[this.pos] === '{') return this.parseObject();\n return coerce(this.readToken());\n }\n\n private readToken(): string {\n const start = this.pos;\n while (this.pos < this.src.length && !/[\\s={}]/.test(this.src[this.pos])) {\n this.pos++;\n }\n return this.src.slice(start, this.pos);\n }\n\n private skipWs(): void {\n while (this.pos < this.src.length && /\\s/.test(this.src[this.pos])) {\n this.pos++;\n }\n }\n}\n\nfunction coerce(raw: string): string | number | boolean {\n if (raw === 'true') return true;\n if (raw === 'false') return false;\n if (/^-?\\d+$/.test(raw)) return parseInt(raw, 10);\n if (/^-?\\d+\\.\\d+$/.test(raw)) return parseFloat(raw);\n return raw;\n}\n","/// Inspired by: https://github.com/stacksjs/ts-printers/blob/45b7d4e6b4292a1a455178ec305610e9feae1573/src/types.ts\n\n// IPP Protocol Types\n\nexport interface PrintConfig {\n defaultPrinter?: string;\n printers?: Record<string, PrinterEntry>;\n timeout?: number;\n verbose: boolean;\n}\n\nexport interface PrinterEntry {\n uri: string;\n name?: string;\n model?: string;\n}\n\n// IPP Constants\n\nexport const IppVersion = {\n V1_1: [1, 1] as const,\n V2_0: [2, 0] as const,\n};\n\nexport const IppOperation = {\n PrintJob: 0x0002,\n ValidateJob: 0x0004,\n CreateJob: 0x0005,\n SendDocument: 0x0006,\n CancelJob: 0x0008,\n GetJobAttributes: 0x0009,\n GetJobs: 0x000a,\n GetPrinterAttributes: 0x000b,\n PausePrinter: 0x0010,\n ResumePrinter: 0x0011,\n IdentifyPrinter: 0x003c,\n} as const;\n\nexport type IppOperationId = (typeof IppOperation)[keyof typeof IppOperation];\n\nexport const IppStatusCode = {\n SuccessfulOk: 0x0000,\n SuccessfulOkIgnoredOrSubstituted: 0x0001,\n SuccessfulOkConflicting: 0x0002,\n ClientErrorBadRequest: 0x0400,\n ClientErrorForbidden: 0x0401,\n ClientErrorNotAuthenticated: 0x0402,\n ClientErrorNotAuthorized: 0x0403,\n ClientErrorNotPossible: 0x0404,\n ClientErrorTimeout: 0x0405,\n ClientErrorNotFound: 0x0406,\n ClientErrorGone: 0x0407,\n ClientErrorDocumentFormatNotSupported: 0x040a,\n ClientErrorAttributesOrValuesNotSupported: 0x040b,\n ServerErrorInternalError: 0x0500,\n ServerErrorOperationNotSupported: 0x0501,\n ServerErrorServiceUnavailable: 0x0502,\n ServerErrorVersionNotSupported: 0x0503,\n ServerErrorDeviceError: 0x0504,\n ServerErrorTemporaryError: 0x0505,\n ServerErrorBusy: 0x0507,\n} as const;\n\nexport const IppTag = {\n // Delimiter tags\n OperationAttributes: 0x01,\n JobAttributes: 0x02,\n EndOfAttributes: 0x03,\n PrinterAttributes: 0x04,\n UnsupportedAttributes: 0x05,\n\n // Out-of-band value tags\n Unsupported: 0x10,\n Unknown: 0x12,\n NoValue: 0x13,\n\n // Integer value tags\n Integer: 0x21,\n Boolean: 0x22,\n Enum: 0x23,\n\n // Octet string value tags\n OctetString: 0x30,\n DateTime: 0x31,\n Resolution: 0x32,\n RangeOfInteger: 0x33,\n BegCollection: 0x34,\n TextWithLanguage: 0x35,\n NameWithLanguage: 0x36,\n EndCollection: 0x37,\n\n // Character string value tags\n TextWithoutLanguage: 0x41,\n NameWithoutLanguage: 0x42,\n Keyword: 0x44,\n Uri: 0x45,\n UriScheme: 0x46,\n Charset: 0x47,\n NaturalLanguage: 0x48,\n MimeMediaType: 0x49,\n MemberAttrName: 0x4a,\n} as const;\n\nexport type IppTagValue = (typeof IppTag)[keyof typeof IppTag];\n\n// IPP Request/Response types\n\nexport interface IppAttribute {\n tag: number;\n name: string;\n value: IppAttributeValue;\n}\n\nexport type IppAttributeValue =\n | string\n | number\n | boolean\n | Uint8Array\n | IppResolution\n | IppCollection\n | IppAttributeValue[];\n\nexport interface IppCollection {\n [memberName: string]: IppAttributeValue;\n}\n\n/** RFC 8011 §5.1.16 'resolution' value. */\nexport interface IppResolution {\n /** Cross-feed direction resolution. */\n x: number;\n /** Feed direction resolution. */\n y: number;\n /** Units: 3 = dots per inch, 4 = dots per centimeter. */\n units: number;\n}\n\nexport const IppResolutionUnit = {\n DotsPerInch: 3,\n DotsPerCentimeter: 4,\n} as const;\n\nexport interface IppAttributeGroup {\n tag: number;\n attributes: IppAttribute[];\n}\n\nexport interface IppRequest {\n version?: readonly [number, number];\n operation: IppOperationId;\n requestId?: number;\n groups: IppAttributeGroup[];\n data?: Uint8Array;\n}\n\nexport interface IppResponse {\n version: [number, number];\n statusCode: number;\n requestId: number;\n groups: IppAttributeGroup[];\n data?: Uint8Array;\n}\n","export function viewOf(arr: Uint8Array): DataView {\n return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);\n}\n\nexport function concat(chunks: Uint8Array[]): Uint8Array<ArrayBuffer> {\n let total = 0;\n for (const c of chunks) total += c.length;\n const result = new Uint8Array(total);\n let offset = 0;\n for (const c of chunks) {\n result.set(c, offset);\n offset += c.length;\n }\n return result;\n}\n","/// Inspired by: https://github.com/stacksjs/ts-printers/blob/45b7d4e6b4292a1a455178ec305610e9feae1573/src/ipp/encoding.ts\n\nimport { IppTag, IppVersion } from './types';\nimport { concat, viewOf } from './utils';\n\nimport type {\n IppAttribute,\n IppAttributeGroup,\n IppAttributeValue,\n IppCollection,\n IppRequest,\n IppResolution,\n IppResponse,\n} from './types';\n\nconst textEncoder = new TextEncoder();\nconst textDecoder = new TextDecoder('utf-8');\n\n/**\n * Encode an IPP request into a binary buffer\n */\nexport function encodeIppRequest(request: IppRequest): Uint8Array<ArrayBuffer> {\n const version = request.version ?? IppVersion.V2_0;\n const requestId = request.requestId ?? 1;\n const chunks: Uint8Array[] = [];\n\n // Version (2 bytes)\n const header = new Uint8Array(8);\n const headerView = viewOf(header);\n headerView.setUint8(0, version[0]);\n headerView.setUint8(1, version[1]);\n // Operation ID (2 bytes)\n headerView.setUint16(2, request.operation, false);\n // Request ID (4 bytes)\n headerView.setUint32(4, requestId, false);\n\n chunks.push(header);\n\n // Attribute groups\n for (const group of request.groups) {\n // Group delimiter tag\n chunks.push(new Uint8Array([group.tag]));\n\n for (const attr of group.attributes) {\n chunks.push(encodeAttribute(attr));\n }\n }\n\n // End-of-attributes tag\n chunks.push(new Uint8Array([IppTag.EndOfAttributes]));\n\n // Optional document data\n if (request.data) {\n chunks.push(request.data);\n }\n\n return concat(chunks);\n}\n\n/**\n * Encode a single IPP attribute\n */\nfunction encodeAttribute(attr: IppAttribute): Uint8Array {\n if (Array.isArray(attr.value)) {\n // Multi-valued attribute: first value has the name, subsequent values have empty name\n const chunks: Uint8Array[] = [];\n for (let i = 0; i < attr.value.length; i++) {\n const singleAttr: IppAttribute = {\n tag: attr.tag,\n name: i === 0 ? attr.name : '',\n value: attr.value[i] as string | number | boolean | Uint8Array,\n };\n chunks.push(encodeSingleAttribute(singleAttr));\n }\n return concat(chunks);\n }\n\n return encodeSingleAttribute(attr);\n}\n\nfunction encodeSingleAttribute(attr: IppAttribute): Uint8Array {\n const nameBytes = textEncoder.encode(attr.name);\n const valueBytes = encodeValue(attr.tag, attr.value);\n\n // tag(1) + name-length(2) + name + value-length(2) + value\n const buf = new Uint8Array(1 + 2 + nameBytes.length + 2 + valueBytes.length);\n const view = viewOf(buf);\n let offset = 0;\n\n view.setUint8(offset, attr.tag);\n offset += 1;\n view.setUint16(offset, nameBytes.length, false);\n offset += 2;\n buf.set(nameBytes, offset);\n offset += nameBytes.length;\n view.setUint16(offset, valueBytes.length, false);\n offset += 2;\n buf.set(valueBytes, offset);\n\n return buf;\n}\n\nfunction encodeValue(tag: number, value: IppAttributeValue): Uint8Array {\n if (Array.isArray(value)) {\n // Should not reach here for multi-valued (handled above)\n return encodeValue(tag, value[0]);\n }\n\n switch (tag) {\n case IppTag.Integer:\n case IppTag.Enum: {\n const buf = new Uint8Array(4);\n viewOf(buf).setInt32(0, value as number, false);\n return buf;\n }\n case IppTag.Boolean: {\n return new Uint8Array([value ? 1 : 0]);\n }\n case IppTag.RangeOfInteger: {\n // Expect a Uint8Array with 8 bytes (lower + upper)\n if (value instanceof Uint8Array) return value;\n const buf = new Uint8Array(8);\n const view = viewOf(buf);\n view.setInt32(0, value as number, false);\n view.setInt32(4, value as number, false);\n return buf;\n }\n case IppTag.Resolution: {\n if (value instanceof Uint8Array) return value;\n const buf = new Uint8Array(9);\n if (\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value) &&\n typeof value.x === 'number' &&\n typeof value.y === 'number' &&\n typeof value.units === 'number'\n ) {\n const view = viewOf(buf);\n view.setInt32(0, value.x, false);\n view.setInt32(4, value.y, false);\n view.setInt8(8, value.units);\n }\n return buf;\n }\n case IppTag.DateTime: {\n if (value instanceof Uint8Array) return value;\n return new Uint8Array(11);\n }\n case IppTag.NoValue:\n case IppTag.Unknown:\n case IppTag.Unsupported: {\n return new Uint8Array(0);\n }\n default: {\n // All string-like types: textWithoutLanguage, nameWithoutLanguage, keyword, uri, charset, etc.\n if (value instanceof Uint8Array) return value;\n return textEncoder.encode(String(value));\n }\n }\n}\n\ninterface DecodeCursor {\n pos: number;\n}\n\nfunction readNameAndValue(\n data: Uint8Array,\n view: DataView,\n cursor: DecodeCursor,\n): { name: string; nameLength: number; rawValue: Uint8Array } {\n const nameLength = view.getUint16(cursor.pos, false);\n cursor.pos += 2;\n const name = textDecoder.decode(\n data.subarray(cursor.pos, cursor.pos + nameLength),\n );\n cursor.pos += nameLength;\n const valueLength = view.getUint16(cursor.pos, false);\n cursor.pos += 2;\n const rawValue = data.subarray(cursor.pos, cursor.pos + valueLength);\n cursor.pos += valueLength;\n return { name, nameLength, rawValue };\n}\n\n/**\n * Parse a collection body up to and including the EndCollection marker.\n * Cursor must be positioned immediately after a BegCollection record.\n */\nfunction parseCollection(\n data: Uint8Array,\n view: DataView,\n cursor: DecodeCursor,\n): IppCollection {\n const collection: IppCollection = {};\n let currentMember: string | null = null;\n\n while (cursor.pos < data.length) {\n const tag = view.getUint8(cursor.pos);\n cursor.pos += 1;\n const { nameLength, rawValue } = readNameAndValue(data, view, cursor);\n\n if (tag === IppTag.EndCollection) {\n return collection;\n }\n\n if (tag === IppTag.MemberAttrName) {\n currentMember = textDecoder.decode(rawValue);\n continue;\n }\n\n if (currentMember === null) {\n // Stray value without a preceding MemberAttrName; skip.\n continue;\n }\n\n const value: IppAttributeValue =\n tag === IppTag.BegCollection\n ? parseCollection(data, view, cursor)\n : decodeValue(tag, rawValue);\n\n if (nameLength === 0 && currentMember in collection) {\n const existing = collection[currentMember];\n if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n collection[currentMember] = [existing, value];\n }\n } else {\n collection[currentMember] = value;\n }\n }\n\n return collection;\n}\n\n/**\n * Decode an IPP response from a binary buffer\n */\nexport function decodeIppResponse(data: Uint8Array): IppResponse {\n const view = viewOf(data);\n const cursor: DecodeCursor = { pos: 0 };\n\n // Version (2 bytes)\n const versionMajor = view.getUint8(cursor.pos);\n cursor.pos += 1;\n const versionMinor = view.getUint8(cursor.pos);\n cursor.pos += 1;\n\n // Status code (2 bytes)\n const statusCode = view.getUint16(cursor.pos, false);\n cursor.pos += 2;\n\n // Request ID (4 bytes)\n const requestId = view.getUint32(cursor.pos, false);\n cursor.pos += 4;\n\n // Parse attribute groups\n const groups: IppAttributeGroup[] = [];\n let currentGroup: IppAttributeGroup | null = null;\n\n while (cursor.pos < data.length) {\n const tag = view.getUint8(cursor.pos);\n cursor.pos += 1;\n\n if (tag === IppTag.EndOfAttributes) {\n break;\n }\n\n if (isDelimiterTag(tag)) {\n currentGroup = { tag, attributes: [] };\n groups.push(currentGroup);\n continue;\n }\n\n if (!currentGroup) {\n continue;\n }\n\n const { name, nameLength, rawValue } = readNameAndValue(data, view, cursor);\n\n const value: IppAttributeValue =\n tag === IppTag.BegCollection\n ? parseCollection(data, view, cursor)\n : decodeValue(tag, rawValue);\n\n if (nameLength === 0 && currentGroup.attributes.length > 0) {\n const prevAttr =\n currentGroup.attributes[currentGroup.attributes.length - 1];\n if (Array.isArray(prevAttr.value)) {\n prevAttr.value.push(value);\n } else {\n prevAttr.value = [prevAttr.value, value];\n }\n } else {\n currentGroup.attributes.push({ tag, name, value });\n }\n }\n\n // Remaining data after end-of-attributes is document data\n const remainingData =\n cursor.pos < data.length ? data.slice(cursor.pos) : undefined;\n\n return {\n version: [versionMajor, versionMinor],\n statusCode,\n requestId,\n groups,\n data: remainingData,\n };\n}\n\nfunction isDelimiterTag(tag: number): boolean {\n return tag >= 0x00 && tag <= 0x0f;\n}\n\nfunction decodeValue(tag: number, raw: Uint8Array): IppAttributeValue {\n switch (tag) {\n case IppTag.Integer:\n case IppTag.Enum: {\n return viewOf(raw).getInt32(0, false);\n }\n case IppTag.Boolean: {\n return viewOf(raw).getUint8(0) !== 0;\n }\n case IppTag.RangeOfInteger: {\n // Return as Uint8Array for now, consumers can interpret\n return raw.slice();\n }\n case IppTag.Resolution: {\n // RFC 8011 §5.1.16: SIGNED-INTEGER xres, SIGNED-INTEGER yres, SIGNED-BYTE units\n const view = viewOf(raw);\n const resolution: IppResolution = {\n x: view.getInt32(0, false),\n y: view.getInt32(4, false),\n units: view.getInt8(8),\n };\n return resolution;\n }\n case IppTag.DateTime: {\n return raw.slice();\n }\n case IppTag.NoValue:\n case IppTag.Unknown:\n case IppTag.Unsupported: {\n return '';\n }\n case IppTag.OctetString: {\n return raw.slice();\n }\n default: {\n // All string types\n return textDecoder.decode(raw);\n }\n }\n}\n\n/**\n * Helper to get a flat map of attribute name -> value from response groups\n */\nexport function getAttributes(\n response: IppResponse,\n groupTag?: number,\n): Record<string, IppAttributeValue> {\n const result: Record<string, IppAttributeValue> = {};\n\n for (const group of response.groups) {\n if (groupTag !== undefined && group.tag !== groupTag) continue;\n\n for (const attr of group.attributes) {\n if (attr.name) {\n result[attr.name] = attr.value;\n }\n }\n }\n\n return result;\n}\n\n/**\n * Helper to get a single attribute value\n */\nexport function getAttribute(\n response: IppResponse,\n name: string,\n groupTag?: number,\n): IppAttributeValue | undefined {\n for (const group of response.groups) {\n if (groupTag !== undefined && group.tag !== groupTag) continue;\n\n for (const attr of group.attributes) {\n if (attr.name === name) {\n return attr.value;\n }\n }\n }\n return undefined;\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pnosolutions/ipp-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Internet Printing Protocol (IPP) encoder/decoder",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ipp",
|
|
7
|
+
"printing",
|
|
8
|
+
"pwg",
|
|
9
|
+
"ipp-everywhere"
|
|
10
|
+
],
|
|
11
|
+
"author": {
|
|
12
|
+
"name": "Nick Popov",
|
|
13
|
+
"email": "nick@pno.solutions"
|
|
14
|
+
},
|
|
15
|
+
"license": "ISC",
|
|
16
|
+
"devEngines": {
|
|
17
|
+
"packageManager": {
|
|
18
|
+
"name": "pnpm",
|
|
19
|
+
"version": "^11.1.1",
|
|
20
|
+
"onFail": "download"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"tsdown": "^0.22.0",
|
|
25
|
+
"typescript": "^6.0.3",
|
|
26
|
+
"vitest": "^4.1.6"
|
|
27
|
+
},
|
|
28
|
+
"type": "module",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"default": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts"
|
|
33
|
+
},
|
|
34
|
+
"./package.json": "./package.json"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsdown",
|
|
38
|
+
"test": "vitest"
|
|
39
|
+
}
|
|
40
|
+
}
|