node-sped-nfe 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.
- package/README.md +75 -0
- package/docs/README.md +49 -0
- package/docs/xml.md +1653 -0
- package/index.js +5 -0
- package/package.json +19 -0
- package/testes/assinar.js +16 -0
- package/testes/nfe.js +166 -0
- package/testes/nfe.json +194 -0
- package/testes/nfe.xml +292 -0
- package/testes/nfe_guara.xml +1 -0
- package/testes/nfe_guara_sign.xml +1 -0
- package/testes/nfe_guara_sign_lote.xml +1 -0
- package/testes/nfe_teste.json +45 -0
- package/utils/eventos.js +32 -0
- package/utils/make.js +798 -0
- package/utils/schemas/consReciNFe_v4.00.xsd +9 -0
- package/utils/schemas/consSitNFe_v4.00.xsd +9 -0
- package/utils/schemas/consStatServ_v4.00.xsd +9 -0
- package/utils/schemas/enviNFe_v4.00.xsd +9 -0
- package/utils/schemas/inutNFe_v4.00.xsd +9 -0
- package/utils/schemas/leiauteConsSitNFe_v4.00.xsd +502 -0
- package/utils/schemas/leiauteConsStatServ_v4.00.xsd +98 -0
- package/utils/schemas/leiauteInutNFe_v4.00.xsd +193 -0
- package/utils/schemas/leiauteNFe_v4.00.xsd +7412 -0
- package/utils/schemas/nfe_v4.00.xsd +9 -0
- package/utils/schemas/procInutNFe_v4.00.xsd +9 -0
- package/utils/schemas/procNFe_v4.00.xsd +9 -0
- package/utils/schemas/retConsReciNFe_v4.00.xsd +9 -0
- package/utils/schemas/retConsSitNFe_v4.00.xsd +9 -0
- package/utils/schemas/retConsStatServ_v4.00.xsd +9 -0
- package/utils/schemas/retEnviNFe_v4.00.xsd +9 -0
- package/utils/schemas/retInutNFe_v4.00.xsd +9 -0
- package/utils/schemas/tiposBasico_v4.00.xsd +598 -0
- package/utils/schemas/xmldsig-core-schema_v1.01.xsd +98 -0
- package/utils/sefaz.js +84 -0
- package/utils/tools.js +277 -0
- package/utils/xmllint.js +244195 -0
@@ -0,0 +1,98 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<!-- ***************************************************-->
|
3
|
+
<!-- *** Schema específico para assinaturas XML ***-->
|
4
|
+
<!-- *** a partir de certificados do padrão (X509) ***-->
|
5
|
+
<!-- *** ICP-Brasil - Projeto Nota Fiscal Eletrônica ***-->
|
6
|
+
<!-- ***************************************************-->
|
7
|
+
<!-- Schema for XML Signatures-->
|
8
|
+
<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" targetNamespace="http://www.w3.org/2000/09/xmldsig#" elementFormDefault="qualified" attributeFormDefault="unqualified" version="0.1">
|
9
|
+
<element name="Signature" type="ds:SignatureType"/>
|
10
|
+
<complexType name="SignatureType">
|
11
|
+
<sequence>
|
12
|
+
<element name="SignedInfo" type="ds:SignedInfoType"/>
|
13
|
+
<element name="SignatureValue" type="ds:SignatureValueType"/>
|
14
|
+
<element name="KeyInfo" type="ds:KeyInfoType"/>
|
15
|
+
</sequence>
|
16
|
+
<attribute name="Id" type="ID" use="optional"/>
|
17
|
+
</complexType>
|
18
|
+
<complexType name="SignatureValueType">
|
19
|
+
<simpleContent>
|
20
|
+
<extension base="base64Binary">
|
21
|
+
<attribute name="Id" type="ID" use="optional"/>
|
22
|
+
</extension>
|
23
|
+
</simpleContent>
|
24
|
+
</complexType>
|
25
|
+
<complexType name="SignedInfoType">
|
26
|
+
<sequence>
|
27
|
+
<element name="CanonicalizationMethod">
|
28
|
+
<complexType>
|
29
|
+
<attribute name="Algorithm" type="anyURI" use="required" fixed="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
|
30
|
+
</complexType>
|
31
|
+
</element>
|
32
|
+
<element name="SignatureMethod">
|
33
|
+
<complexType>
|
34
|
+
<attribute name="Algorithm" type="anyURI" use="required" fixed="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
|
35
|
+
</complexType>
|
36
|
+
</element>
|
37
|
+
<element name="Reference" type="ds:ReferenceType"/>
|
38
|
+
</sequence>
|
39
|
+
<attribute name="Id" type="ID" use="optional"/>
|
40
|
+
</complexType>
|
41
|
+
<complexType name="ReferenceType">
|
42
|
+
<sequence>
|
43
|
+
<element name="Transforms" type="ds:TransformsType">
|
44
|
+
<!-- Garante a unicidade do atributo -->
|
45
|
+
<unique name="unique_Transf_Alg">
|
46
|
+
<selector xpath="./*"/>
|
47
|
+
<field xpath="@Algorithm"/>
|
48
|
+
</unique>
|
49
|
+
</element>
|
50
|
+
<element name="DigestMethod">
|
51
|
+
<complexType>
|
52
|
+
<attribute name="Algorithm" type="anyURI" use="required" fixed="http://www.w3.org/2000/09/xmldsig#sha1"/>
|
53
|
+
</complexType>
|
54
|
+
</element>
|
55
|
+
<element name="DigestValue" type="ds:DigestValueType"/>
|
56
|
+
</sequence>
|
57
|
+
<attribute name="Id" type="ID" use="optional"/>
|
58
|
+
<attribute name="URI" use="required">
|
59
|
+
<simpleType>
|
60
|
+
<restriction base="anyURI">
|
61
|
+
<minLength value="2"/>
|
62
|
+
</restriction>
|
63
|
+
</simpleType>
|
64
|
+
</attribute>
|
65
|
+
<attribute name="Type" type="anyURI" use="optional"/>
|
66
|
+
</complexType>
|
67
|
+
<complexType name="TransformsType">
|
68
|
+
<sequence>
|
69
|
+
<element name="Transform" type="ds:TransformType" minOccurs="2" maxOccurs="2"/>
|
70
|
+
</sequence>
|
71
|
+
</complexType>
|
72
|
+
<complexType name="TransformType">
|
73
|
+
<sequence minOccurs="0" maxOccurs="unbounded">
|
74
|
+
<element name="XPath" type="string"/>
|
75
|
+
</sequence>
|
76
|
+
<attribute name="Algorithm" type="ds:TTransformURI" use="required"/>
|
77
|
+
</complexType>
|
78
|
+
<complexType name="KeyInfoType">
|
79
|
+
<sequence>
|
80
|
+
<element name="X509Data" type="ds:X509DataType"/>
|
81
|
+
</sequence>
|
82
|
+
<attribute name="Id" type="ID" use="optional"/>
|
83
|
+
</complexType>
|
84
|
+
<complexType name="X509DataType">
|
85
|
+
<sequence>
|
86
|
+
<element name="X509Certificate" type="base64Binary"/>
|
87
|
+
</sequence>
|
88
|
+
</complexType>
|
89
|
+
<simpleType name="DigestValueType">
|
90
|
+
<restriction base="base64Binary"/>
|
91
|
+
</simpleType>
|
92
|
+
<simpleType name="TTransformURI">
|
93
|
+
<restriction base="anyURI">
|
94
|
+
<enumeration value="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
|
95
|
+
<enumeration value="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
|
96
|
+
</restriction>
|
97
|
+
</simpleType>
|
98
|
+
</schema>
|
package/utils/sefaz.js
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
'use strict'
|
2
|
+
import axios from "axios"
|
3
|
+
import https from "https"
|
4
|
+
|
5
|
+
|
6
|
+
class Instance {
|
7
|
+
constructor(opts) {
|
8
|
+
const { baseURL, ca, cert, key } = opts
|
9
|
+
|
10
|
+
const AgentOptions = Object.assign(
|
11
|
+
{
|
12
|
+
cert: cert,
|
13
|
+
key: key,
|
14
|
+
ca: ca,
|
15
|
+
},
|
16
|
+
{ ...opts.httpsOptions }
|
17
|
+
)
|
18
|
+
|
19
|
+
const httpsAgent = new https.Agent(AgentOptions)
|
20
|
+
|
21
|
+
const requestOptions = Object.assign(
|
22
|
+
{
|
23
|
+
baseURL: baseURL,
|
24
|
+
headers: {
|
25
|
+
'User-Agent': `node-nfe/1.0`,
|
26
|
+
'Content-Type': 'application/soap+xml; charset=utf-8',
|
27
|
+
},
|
28
|
+
httpsAgent: httpsAgent,
|
29
|
+
timeout: 60000,
|
30
|
+
},
|
31
|
+
{ ...opts.requestOptions }
|
32
|
+
)
|
33
|
+
|
34
|
+
const instance = axios.create({
|
35
|
+
...requestOptions,
|
36
|
+
})
|
37
|
+
|
38
|
+
this.instance = instance
|
39
|
+
}
|
40
|
+
|
41
|
+
/**
|
42
|
+
* @returns {Promise<{status: number, data: string}>}
|
43
|
+
*/
|
44
|
+
async request(config) {
|
45
|
+
try {
|
46
|
+
const response = await this.instance(config)
|
47
|
+
|
48
|
+
const { status, data } = response
|
49
|
+
|
50
|
+
return { status, data }
|
51
|
+
} catch (error) {
|
52
|
+
if (error.response) {
|
53
|
+
const { status, data } = error.response
|
54
|
+
|
55
|
+
return { status, data }
|
56
|
+
} else if (error.request) {
|
57
|
+
if (error.code === 'ECONNABORTED') {
|
58
|
+
const retorno = {
|
59
|
+
status: 504,
|
60
|
+
data: `<error>${error.message || error}</error>`,
|
61
|
+
}
|
62
|
+
|
63
|
+
return retorno
|
64
|
+
}
|
65
|
+
|
66
|
+
const retorno = {
|
67
|
+
status: 502,
|
68
|
+
data: `<error>${error.message || error}</error>`,
|
69
|
+
}
|
70
|
+
|
71
|
+
return retorno
|
72
|
+
} else {
|
73
|
+
const retorno = {
|
74
|
+
status: 500,
|
75
|
+
data: `<error>${error.message || error}</error>`,
|
76
|
+
}
|
77
|
+
|
78
|
+
return retorno
|
79
|
+
}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
module.exports = Instance
|
package/utils/tools.js
ADDED
@@ -0,0 +1,277 @@
|
|
1
|
+
import { XMLParser, XMLBuilder, XMLValidator } from "fast-xml-parser";
|
2
|
+
import pem from "pem";
|
3
|
+
import https from "https";
|
4
|
+
import { spawnSync } from "child_process"
|
5
|
+
import tmp from "tmp"
|
6
|
+
import crypto from "crypto";
|
7
|
+
import { urlServicos } from "./eventos.js"
|
8
|
+
import fs from "fs"
|
9
|
+
|
10
|
+
|
11
|
+
class Tools {
|
12
|
+
#cert = new Object();
|
13
|
+
#xmlTools = {
|
14
|
+
XMLParser: null,
|
15
|
+
XMLBuilder: null,
|
16
|
+
}
|
17
|
+
#pem = null;
|
18
|
+
#config = null;
|
19
|
+
|
20
|
+
constructor(config = { mod: "", xmllint: 'xmllint' }, certificado = { pfx: "", senha: "" }) {
|
21
|
+
//Configurar certificado
|
22
|
+
this.#config = config;
|
23
|
+
this.#cert = certificado;
|
24
|
+
this.#xmlTools.XMLBuilder = new XMLBuilder({
|
25
|
+
ignoreAttributes: false,
|
26
|
+
attributeNamePrefix: "@",
|
27
|
+
parseTagValue: false, // Evita conversão automática de valores
|
28
|
+
parseNodeValue: false, // Mantém valores como strings
|
29
|
+
});
|
30
|
+
this.#xmlTools.XMLParser = new XMLParser({
|
31
|
+
ignoreAttributes: false,
|
32
|
+
attributeNamePrefix: "@",
|
33
|
+
parseTagValue: false, // Evita conversão automática de valores
|
34
|
+
parseNodeValue: false, // Mantém valores como strings
|
35
|
+
});
|
36
|
+
}
|
37
|
+
|
38
|
+
sefazEnviaLote(xml = [], data = { idLote: 1, indSinc: 0, compactar: false }) {
|
39
|
+
return new Promise(async (resvol, reject) => {
|
40
|
+
if (typeof data.idLote == "undefined") data.idLote = 1;
|
41
|
+
if (typeof data.indSinc == "undefined") data.indSinc = 0;
|
42
|
+
if (typeof data.compactar == "undefined") data.compactar = false;
|
43
|
+
|
44
|
+
await this.#certTools();
|
45
|
+
let xmlLote = {
|
46
|
+
"soap:Envelope": {
|
47
|
+
"@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
48
|
+
"@xmlns:xsd": "http://www.w3.org/2001/XMLSchema",
|
49
|
+
"@xmlns:soap": "http://www.w3.org/2003/05/soap-envelope",
|
50
|
+
"soap:Body": {
|
51
|
+
"nfeDadosMsg": {
|
52
|
+
"@xmlns": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeAutorizacao4",
|
53
|
+
"enviNFe": {
|
54
|
+
...{
|
55
|
+
"@xmlns": "http://www.portalfiscal.inf.br/nfe",
|
56
|
+
"@versao": "4.00",
|
57
|
+
"idLote": data.idLote,
|
58
|
+
"indSinc": data.indSinc,
|
59
|
+
},
|
60
|
+
...(await this.xml2json(xml))
|
61
|
+
}
|
62
|
+
}
|
63
|
+
}
|
64
|
+
},
|
65
|
+
}
|
66
|
+
xmlLote = await this.json2xml(xmlLote);
|
67
|
+
fs.writeFileSync("testes/nfe_guara_sign_lote.xml", xmlLote, { encoding: "utf8" });
|
68
|
+
try {
|
69
|
+
const req = https.request(urlServicos[`${this.#config.cUF}`][`mod_${this.#config.mod}`].NFeAutorizacao[(this.#config.tpAmb == 1 ? "producao" : "homologacao")], {
|
70
|
+
...{
|
71
|
+
method: 'POST',
|
72
|
+
headers: {
|
73
|
+
'Content-Type': 'application/soap+xml; charset=utf-8',
|
74
|
+
'Content-Length': xmlLote.length,
|
75
|
+
},
|
76
|
+
rejectUnauthorized: false
|
77
|
+
},
|
78
|
+
...this.#pem
|
79
|
+
}, (res) => {
|
80
|
+
let data = '';
|
81
|
+
|
82
|
+
res.on('data', (chunk) => {
|
83
|
+
data += chunk;
|
84
|
+
});
|
85
|
+
|
86
|
+
res.on('end', () => {
|
87
|
+
resvol(data);
|
88
|
+
});
|
89
|
+
});
|
90
|
+
|
91
|
+
req.on('error', (erro) => {
|
92
|
+
reject(erro);
|
93
|
+
});
|
94
|
+
req.write(xmlLote);
|
95
|
+
req.end();
|
96
|
+
} catch (erro) {
|
97
|
+
reject(erro);
|
98
|
+
}
|
99
|
+
})
|
100
|
+
}
|
101
|
+
|
102
|
+
async xmlSign(xml, data = {}) {
|
103
|
+
return new Promise(async (resvol, reject) => {
|
104
|
+
if (data.tag === undefined) data.tag = "infNFe";
|
105
|
+
|
106
|
+
//Obter a tag que ira ser assinada
|
107
|
+
xml = await this.xml2json(xml);
|
108
|
+
let pem = await this.getCertificado();
|
109
|
+
const sign = crypto.createSign('RSA-SHA1'); // Correção: Alterado para RSA-SHA1
|
110
|
+
let signedInfo = {
|
111
|
+
"SignedInfo": {
|
112
|
+
"@xmlns": "http://www.w3.org/2000/09/xmldsig#",
|
113
|
+
"CanonicalizationMethod": {
|
114
|
+
"@Algorithm": "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
|
115
|
+
},
|
116
|
+
"SignatureMethod": {
|
117
|
+
"@Algorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1" // Mantém SHA1
|
118
|
+
},
|
119
|
+
"Reference": {
|
120
|
+
"@URI": `#${xml.NFe.infNFe['@Id']}`,
|
121
|
+
"Transforms": {
|
122
|
+
"Transform": [
|
123
|
+
{
|
124
|
+
"@Algorithm": "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
|
125
|
+
},
|
126
|
+
{
|
127
|
+
"@Algorithm": "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
|
128
|
+
},
|
129
|
+
]
|
130
|
+
},
|
131
|
+
"DigestMethod": {
|
132
|
+
"@Algorithm": "http://www.w3.org/2000/09/xmldsig#sha1" // Mantém SHA1
|
133
|
+
},
|
134
|
+
"DigestValue": crypto.createHash('sha1').update(await this.json2xml({ infNFe: xml.NFe.infNFe }), 'utf8') // Mantém Hash SHA1
|
135
|
+
.digest('base64')
|
136
|
+
|
137
|
+
}
|
138
|
+
}
|
139
|
+
}
|
140
|
+
sign.update(await this.json2xml(signedInfo), 'utf8');
|
141
|
+
xml.NFe.Signature = {
|
142
|
+
...signedInfo,
|
143
|
+
...{
|
144
|
+
"SignatureValue": sign.sign(pem.key, 'base64'),
|
145
|
+
"KeyInfo": {
|
146
|
+
"X509Data": {
|
147
|
+
"X509Certificate": pem.cert.replace(/-----BEGIN CERTIFICATE-----/g, '').replace(/-----END CERTIFICATE-----/g, '').replace(/\r\n/g, '')
|
148
|
+
}
|
149
|
+
},
|
150
|
+
"@xmlns": "http://www.w3.org/2000/09/xmldsig#"
|
151
|
+
}
|
152
|
+
}
|
153
|
+
this.json2xml(xml).then(async res => {
|
154
|
+
this.#xmlValido(res);
|
155
|
+
resvol(res);
|
156
|
+
}).catch(err => {
|
157
|
+
reject(err)
|
158
|
+
})
|
159
|
+
})
|
160
|
+
}
|
161
|
+
|
162
|
+
async xml2json(xml) {
|
163
|
+
return new Promise((resvol, reject) => {
|
164
|
+
resvol(this.#xmlTools.XMLParser.parse(xml))
|
165
|
+
})
|
166
|
+
}
|
167
|
+
|
168
|
+
async json2xml(obj) {
|
169
|
+
return new Promise((resvol, reject) => {
|
170
|
+
resvol(this.#xmlTools.XMLBuilder.build(obj))
|
171
|
+
})
|
172
|
+
}
|
173
|
+
|
174
|
+
async getCertificado() {
|
175
|
+
return new Promise(async (resvol, reject) => {
|
176
|
+
await this.#certTools().then(resvol).catch(reject)
|
177
|
+
})
|
178
|
+
}
|
179
|
+
|
180
|
+
//Consulta status sefaz
|
181
|
+
async sefazStatus() {
|
182
|
+
return new Promise(async (resvol, reject) => {
|
183
|
+
await this.#certTools();
|
184
|
+
|
185
|
+
if (typeof this.#config.cUF == "undefined") throw "sefazStatus({...cUF}) -> não definido!";
|
186
|
+
if (typeof this.#config.tpAmb == "undefined") throw "sefazStatus({...tpAmb}) -> não definido!";
|
187
|
+
if (typeof this.#config.mod == "undefined") throw "sefazStatus({...mod}) -> não definido!";
|
188
|
+
|
189
|
+
let xml = {
|
190
|
+
"soap:Envelope": {
|
191
|
+
"@xmlns:soap": "http://www.w3.org/2003/05/soap-envelope",
|
192
|
+
"@xmlns:nfe": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeStatusServico4",
|
193
|
+
"soap:Body": {
|
194
|
+
"nfe:nfeDadosMsg": {
|
195
|
+
"consStatServ": {
|
196
|
+
"@versao": "4.00",
|
197
|
+
"@xmlns": "http://www.portalfiscal.inf.br/nfe",
|
198
|
+
"tpAmb": this.#config.tpAmb,
|
199
|
+
"cUF": this.#config.cUF,
|
200
|
+
"xServ": "STATUS"
|
201
|
+
}
|
202
|
+
}
|
203
|
+
}
|
204
|
+
}
|
205
|
+
}
|
206
|
+
|
207
|
+
try {
|
208
|
+
let tempBuild = new XMLBuilder({
|
209
|
+
ignoreAttributes: false,
|
210
|
+
attributeNamePrefix: "@"
|
211
|
+
});
|
212
|
+
xml = tempBuild.build(xml);
|
213
|
+
|
214
|
+
const req = https.request(urlServicos[`${this.#config.cUF}`][`mod_${this.#config.mod}`].NfeStatusServico[(this.#config.tpAmb == 1 ? "producao" : "homologacao")], {
|
215
|
+
...{
|
216
|
+
method: 'POST',
|
217
|
+
headers: {
|
218
|
+
'Content-Type': 'application/soap+xml; charset=utf-8',
|
219
|
+
'Content-Length': xml.length,
|
220
|
+
},
|
221
|
+
rejectUnauthorized: false
|
222
|
+
},
|
223
|
+
...this.#pem
|
224
|
+
}, (res) => {
|
225
|
+
let data = '';
|
226
|
+
|
227
|
+
res.on('data', (chunk) => {
|
228
|
+
data += chunk;
|
229
|
+
});
|
230
|
+
|
231
|
+
res.on('end', () => {
|
232
|
+
resvol(data);
|
233
|
+
});
|
234
|
+
});
|
235
|
+
|
236
|
+
req.on('error', (erro) => {
|
237
|
+
reject(erro);
|
238
|
+
});
|
239
|
+
|
240
|
+
req.write(xml);
|
241
|
+
req.end();
|
242
|
+
} catch (erro) {
|
243
|
+
reject(erro);
|
244
|
+
}
|
245
|
+
})
|
246
|
+
}
|
247
|
+
|
248
|
+
|
249
|
+
//Validar XML da NFe, somente apos assinar
|
250
|
+
async #xmlValido(xml) {
|
251
|
+
let xmlFile = tmp.fileSync({ mode: 0o644, prefix: 'xml-', postfix: '.xml' });
|
252
|
+
fs.writeFileSync(xmlFile.name, xml, { encoding: "utf8" });
|
253
|
+
let verif = spawnSync(this.#config.xmllint, ['--noout', '--schema', `./utils/schemas/nfe_v4.00.xsd`, xmlFile.name], { encoding: 'utf8' });
|
254
|
+
xmlFile.removeCallback();
|
255
|
+
|
256
|
+
if (verif.errno == -4058) {
|
257
|
+
//xmllint - não instalado ou informado path
|
258
|
+
throw ("Biblioteca xmllint não encontrado!")
|
259
|
+
} else if (!verif.stderr.includes(".xml validates")) {
|
260
|
+
throw (verif.stderr)
|
261
|
+
}
|
262
|
+
return 1;
|
263
|
+
}
|
264
|
+
|
265
|
+
|
266
|
+
//Extrair dados do certificado pem
|
267
|
+
#certTools() {
|
268
|
+
return new Promise(async (resvol, reject) => {
|
269
|
+
if (this.#pem != null) resvol(this.#pem);
|
270
|
+
pem.readPkcs12(this.#cert.pfx, { p12Password: this.#cert.senha }, (err, myPem) => {
|
271
|
+
this.#pem = myPem;
|
272
|
+
resvol(myPem);
|
273
|
+
});
|
274
|
+
})
|
275
|
+
}
|
276
|
+
}
|
277
|
+
export { Tools }
|