首次完整推送,

V:1.20240808.006
This commit is contained in:
fm453
2024-08-13 18:32:37 +08:00
parent 15be3e9373
commit c62d15b288
939 changed files with 111777 additions and 0 deletions

View File

@ -0,0 +1,16 @@
const AlipayBase = require('../alipayBase')
const protocols = require('./protocols')
module.exports = class Auth extends AlipayBase {
constructor (options) {
super(options)
this._protocols = protocols
}
async code2Session (code) {
const result = await this._exec('alipay.system.oauth.token', {
grantType: 'authorization_code',
code
})
return result
}
}

View File

@ -0,0 +1,10 @@
module.exports = {
code2Session: {
// args (fromArgs) {
// return fromArgs
// },
returnValue: {
openid: 'userId'
}
}
}

View File

@ -0,0 +1,231 @@
const {
camel2snakeJson,
snake2camelJson,
getOffsetDate,
getFullTimeStr
} = require('../../../common/utils')
const crypto = require('crypto')
const ALIPAY_ALGORITHM_MAPPING = {
RSA: 'RSA-SHA1',
RSA2: 'RSA-SHA256'
}
module.exports = class AlipayBase {
constructor (options = {}) {
if (!options.appId) throw new Error('appId required')
if (!options.privateKey) throw new Error('privateKey required')
const defaultOptions = {
gateway: 'https://openapi.alipay.com/gateway.do',
timeout: 5000,
charset: 'utf-8',
version: '1.0',
signType: 'RSA2',
timeOffset: -new Date().getTimezoneOffset() / 60,
keyType: 'PKCS8'
}
if (options.sandbox) {
options.gateway = 'https://openapi.alipaydev.com/gateway.do'
}
this.options = Object.assign({}, defaultOptions, options)
const privateKeyType =
this.options.keyType === 'PKCS8' ? 'PRIVATE KEY' : 'RSA PRIVATE KEY'
this.options.privateKey = this._formatKey(
this.options.privateKey,
privateKeyType
)
if (this.options.alipayPublicKey) {
this.options.alipayPublicKey = this._formatKey(
this.options.alipayPublicKey,
'PUBLIC KEY'
)
}
}
_formatKey (key, type) {
return `-----BEGIN ${type}-----\n${key}\n-----END ${type}-----`
}
_formatUrl (url, params) {
let requestUrl = url
// 需要放在 url 中的参数列表
const urlArgs = [
'app_id',
'method',
'format',
'charset',
'sign_type',
'sign',
'timestamp',
'version',
'notify_url',
'return_url',
'auth_token',
'app_auth_token'
]
for (const key in params) {
if (urlArgs.indexOf(key) > -1) {
const val = encodeURIComponent(params[key])
requestUrl = `${requestUrl}${requestUrl.includes('?') ? '&' : '?'
}${key}=${val}`
// 删除 postData 中对应的数据
delete params[key]
}
}
return { execParams: params, url: requestUrl }
}
_getSign (method, params) {
const bizContent = params.bizContent || null
delete params.bizContent
const signParams = Object.assign({
method,
appId: this.options.appId,
charset: this.options.charset,
version: this.options.version,
signType: this.options.signType,
timestamp: getFullTimeStr(getOffsetDate(this.options.timeOffset))
}, params)
if (bizContent) {
signParams.bizContent = JSON.stringify(camel2snakeJson(bizContent))
}
// params key 驼峰转下划线
const decamelizeParams = camel2snakeJson(signParams)
// 排序
const signStr = Object.keys(decamelizeParams)
.sort()
.map((key) => {
let data = decamelizeParams[key]
if (Array.prototype.toString.call(data) !== '[object String]') {
data = JSON.stringify(data)
}
return `${key}=${data}`
})
.join('&')
// 计算签名
const sign = crypto
.createSign(ALIPAY_ALGORITHM_MAPPING[this.options.signType])
.update(signStr, 'utf8')
.sign(this.options.privateKey, 'base64')
return Object.assign(decamelizeParams, { sign })
}
async _exec (method, params = {}, option = {}) {
// 计算签名
const signData = this._getSign(method, params)
const { url, execParams } = this._formatUrl(this.options.gateway, signData)
const { status, data } = await uniCloud.httpclient.request(url, {
method: 'POST',
data: execParams,
// 按 text 返回(为了验签)
dataType: 'text',
timeout: this.options.timeout
})
if (status !== 200) throw new Error('request fail')
/**
* 示例响应格式
* {"alipay_trade_precreate_response":
* {"code": "10000","msg": "Success","out_trade_no": "111111","qr_code": "https:\/\/"},
* "sign": "abcde="
* }
* 或者
* {"error_response":
* {"code":"40002","msg":"Invalid Arguments","sub_code":"isv.code-invalid","sub_msg":"授权码code无效"},
* }
*/
const result = JSON.parse(data)
const responseKey = `${method.replace(/\./g, '_')}_response`
const response = result[responseKey]
const errorResponse = result.error_response
if (response) {
// 按字符串验签
const validateSuccess = option.validateSign ? this._checkResponseSign(data, responseKey) : true
if (validateSuccess) {
if (!response.code || response.code === '10000') {
const errCode = 0
const errMsg = response.msg || ''
return {
errCode,
errMsg,
...snake2camelJson(response)
}
}
const msg = response.sub_code ? `${response.sub_code} ${response.sub_msg}` : `${response.msg || 'unkonwn error'}`
throw new Error(msg)
} else {
throw new Error('check sign error')
}
} else if (errorResponse) {
throw new Error(errorResponse.sub_msg || errorResponse.msg || 'request fail')
}
throw new Error('request fail')
}
_checkResponseSign (signStr, responseKey) {
if (!this.options.alipayPublicKey || this.options.alipayPublicKey === '') {
console.warn('options.alipayPublicKey is empty')
// 支付宝公钥不存在时不做验签
return true
}
// 带验签的参数不存在时返回失败
if (!signStr) { return false }
// 根据服务端返回的结果截取需要验签的目标字符串
const validateStr = this._getSignStr(signStr, responseKey)
// 服务端返回的签名
const serverSign = JSON.parse(signStr).sign
// 参数存在,并且是正常的结果(不包含 sub_code时才验签
const verifier = crypto.createVerify(ALIPAY_ALGORITHM_MAPPING[this.options.signType])
verifier.update(validateStr, 'utf8')
return verifier.verify(this.options.alipayPublicKey, serverSign, 'base64')
}
_getSignStr (originStr, responseKey) {
// 待签名的字符串
let validateStr = originStr.trim()
// 找到 xxx_response 开始的位置
const startIndex = originStr.indexOf(`${responseKey}"`)
// 找到最后一个 “"sign"” 字符串的位置(避免)
const lastIndex = originStr.lastIndexOf('"sign"')
/**
* 删除 xxx_response 及之前的字符串
* 假设原始字符串为
* {"xxx_response":{"code":"10000"},"sign":"jumSvxTKwn24G5sAIN"}
* 删除后变为
* :{"code":"10000"},"sign":"jumSvxTKwn24G5sAIN"}
*/
validateStr = validateStr.substr(startIndex + responseKey.length + 1)
/**
* 删除最后一个 "sign" 及之后的字符串
* 删除后变为
* :{"code":"10000"},
* {} 之间就是待验签的字符串
*/
validateStr = validateStr.substr(0, lastIndex)
// 删除第一个 { 之前的任何字符
validateStr = validateStr.replace(/^[^{]*{/g, '{')
// 删除最后一个 } 之后的任何字符
validateStr = validateStr.replace(/\}([^}]*)$/g, '}')
return validateStr
}
}

View File

@ -0,0 +1,79 @@
const rsaPublicKeyPem = require('../rsa-public-key-pem')
const {
jwtVerify
} = require('../../../npm/index')
let authKeysCache = null
module.exports = class Auth {
constructor (options) {
this.options = Object.assign({
baseUrl: 'https://appleid.apple.com',
timeout: 10000
}, options)
}
async _fetch (url, options) {
const { baseUrl } = this.options
return uniCloud.httpclient.request(baseUrl + url, options)
}
async verifyIdentityToken (identityToken) {
// 解密出kid拿取key
const jwtHeader = identityToken.split('.')[0]
const { kid } = JSON.parse(Buffer.from(jwtHeader, 'base64').toString())
let authKeys
if (authKeysCache) {
authKeys = authKeysCache
} else {
authKeys = await this.getAuthKeys()
authKeysCache = authKeys
}
const usedKey = authKeys.find(item => item.kid === kid)
/**
* identityToken 格式
*
* {
* iss: 'https://appleid.apple.com',
* aud: 'io.dcloud.hellouniapp',
* exp: 1610626724,
* iat: 1610540324,
* sub: '000628.30119d332d9b45a3be4a297f9391fd5c.0403',
* c_hash: 'oFfgewoG36cJX00KUbj45A',
* email: 'x2awmap99s@privaterelay.appleid.com',
* email_verified: 'true',
* is_private_email: 'true',
* auth_time: 1610540324,
* nonce_supported: true
* }
*/
const payload = jwtVerify(
identityToken,
rsaPublicKeyPem(usedKey.n, usedKey.e),
{
algorithms: usedKey.alg
}
)
if (payload.iss !== 'https://appleid.apple.com' || payload.aud !== this.options.bundleId) {
throw new Error('Invalid identity token')
}
return {
openid: payload.sub,
email: payload.email,
emailVerified: payload.email_verified === 'true',
isPrivateEmail: payload.is_private_email === 'true'
}
}
async getAuthKeys () {
const { status, data } = await this._fetch('/auth/keys', {
method: 'GET',
dataType: 'json',
timeout: this.options.timeout
})
if (status !== 200) throw new Error('request https://appleid.apple.com/auth/keys fail')
return data.keys
}
}

View File

@ -0,0 +1,64 @@
// http://stackoverflow.com/questions/18835132/xml-to-pem-in-node-js
/* eslint-disable camelcase */
function rsaPublicKeyPem (modulus_b64, exponent_b64) {
const modulus = Buffer.from(modulus_b64, 'base64')
const exponent = Buffer.from(exponent_b64, 'base64')
let modulus_hex = modulus.toString('hex')
let exponent_hex = exponent.toString('hex')
modulus_hex = prepadSigned(modulus_hex)
exponent_hex = prepadSigned(exponent_hex)
const modlen = modulus_hex.length / 2
const explen = exponent_hex.length / 2
const encoded_modlen = encodeLengthHex(modlen)
const encoded_explen = encodeLengthHex(explen)
const encoded_pubkey = '30' +
encodeLengthHex(
modlen +
explen +
encoded_modlen.length / 2 +
encoded_explen.length / 2 + 2
) +
'02' + encoded_modlen + modulus_hex +
'02' + encoded_explen + exponent_hex
const der_b64 = Buffer.from(encoded_pubkey, 'hex').toString('base64')
const pem = '-----BEGIN RSA PUBLIC KEY-----\n' +
der_b64.match(/.{1,64}/g).join('\n') +
'\n-----END RSA PUBLIC KEY-----\n'
return pem
}
function prepadSigned (hexStr) {
const msb = hexStr[0]
if (msb < '0' || msb > '7') {
return '00' + hexStr
} else {
return hexStr
}
}
function toHex (number) {
const nstr = number.toString(16)
if (nstr.length % 2) return '0' + nstr
return nstr
}
// encode ASN.1 DER length field
// if <=127, short form
// if >=128, long form
function encodeLengthHex (n) {
if (n <= 127) return toHex(n)
else {
const n_hex = toHex(n)
const length_of_length_byte = 128 + n_hex.length / 2 // 0x80+numbytes
return toHex(length_of_length_byte) + n_hex
}
}
module.exports = rsaPublicKeyPem

View File

@ -0,0 +1,36 @@
const WxAccount = require('./weixin/account/index')
const QQAccount = require('./qq/account/index')
const AliAccount = require('./alipay/account/index')
const AppleAccount = require('./apple/account/index')
const createApi = require('./share/create-api')
module.exports = {
initWeixin: function () {
const oauthConfig = this.configUtils.getOauthConfig({ provider: 'weixin' })
return createApi(WxAccount, {
appId: oauthConfig.appid,
secret: oauthConfig.appsecret
})
},
initQQ: function () {
const oauthConfig = this.configUtils.getOauthConfig({ provider: 'qq' })
return createApi(QQAccount, {
appId: oauthConfig.appid,
secret: oauthConfig.appsecret
})
},
initAlipay: function () {
const oauthConfig = this.configUtils.getOauthConfig({ provider: 'alipay' })
return createApi(AliAccount, {
appId: oauthConfig.appid,
privateKey: oauthConfig.privateKey
})
},
initApple: function () {
const oauthConfig = this.configUtils.getOauthConfig({ provider: 'apple' })
return createApi(AppleAccount, {
bundleId: oauthConfig.bundleId
})
}
}

View File

@ -0,0 +1,97 @@
const {
UniCloudError
} = require('../../../../common/error')
const {
resolveUrl
} = require('../../../../common/utils')
const {
callQQOpenApi
} = require('../normalize')
module.exports = class Auth {
constructor (options) {
this.options = Object.assign({
baseUrl: 'https://graph.qq.com',
timeout: 5000
}, options)
}
async _requestQQOpenapi ({ name, url, data, options }) {
const defaultOptions = {
method: 'GET',
dataType: 'json',
dataAsQueryString: true,
timeout: this.options.timeout
}
const result = await callQQOpenApi({
name: `auth.${name}`,
url: resolveUrl(this.options.baseUrl, url),
data,
options,
defaultOptions
})
return result
}
async getUserInfo ({
accessToken,
openid
} = {}) {
const url = '/user/get_user_info'
const result = await this._requestQQOpenapi({
name: 'getUserInfo',
url,
data: {
oauthConsumerKey: this.options.appId,
accessToken,
openid
}
})
return {
nickname: result.nickname,
avatar: result.figureurl_qq_1
}
}
async getOpenidByToken ({
accessToken
} = {}) {
const url = '/oauth2.0/me'
const result = await this._requestQQOpenapi({
name: 'getOpenidByToken',
url,
data: {
accessToken,
unionid: 1,
fmt: 'json'
}
})
if (result.clientId !== this.options.appId) {
throw new UniCloudError({
code: 'APPID_NOT_MATCH',
message: 'appid not match'
})
}
return {
openid: result.openid,
unionid: result.unionid
}
}
async code2Session ({
code
} = {}) {
const url = 'https://api.q.qq.com/sns/jscode2session'
const result = await this._requestQQOpenapi({
name: 'getOpenidByToken',
url,
data: {
grant_type: 'authorization_code',
appid: this.options.appId,
secret: this.options.secret,
js_code: code
}
})
return result
}
}

View File

@ -0,0 +1,85 @@
const {
UniCloudError
} = require('../../../common/error')
const {
camel2snakeJson,
snake2camelJson
} = require('../../../common/utils')
function generateApiResult (apiName, data) {
if (data.ret || data.error) {
// 这三种都是qq的错误码规范
const code = data.ret || data.error || data.errcode || -2
const message = data.msg || data.error_description || data.errmsg || `${apiName} fail`
throw new UniCloudError({
code,
message
})
} else {
delete data.ret
delete data.msg
delete data.error
delete data.error_description
delete data.errcode
delete data.errmsg
return {
...data,
errMsg: `${apiName} ok`,
errCode: 0
}
}
}
function nomalizeError (apiName, error) {
throw new UniCloudError({
code: error.code || -2,
message: error.message || `${apiName} fail`
})
}
async function callQQOpenApi ({
name,
url,
data,
options,
defaultOptions
}) {
options = Object.assign({}, defaultOptions, options, { data: camel2snakeJson(Object.assign({}, data)) })
let result
try {
result = await uniCloud.httpclient.request(url, options)
} catch (e) {
return nomalizeError(name, e)
}
let resData = result.data
const contentType = result.headers['content-type']
if (
Buffer.isBuffer(resData) &&
(contentType.indexOf('text/plain') === 0 ||
contentType.indexOf('application/json') === 0)
) {
try {
resData = JSON.parse(resData.toString())
} catch (e) {
resData = resData.toString()
}
} else if (Buffer.isBuffer(resData)) {
resData = {
buffer: resData,
contentType
}
}
return snake2camelJson(
generateApiResult(
name,
resData || {
errCode: -2,
errMsg: 'Request failed'
}
)
)
}
module.exports = {
callQQOpenApi
}

View File

@ -0,0 +1,73 @@
const {
isFn,
isPlainObject
} = require('../../../common/utils')
// 注意:不进行递归处理
function parseParams (params = {}, rule) {
if (!rule || !params) {
return params
}
const internalKeys = ['_pre', '_purify', '_post']
// 转换之前的处理
if (rule._pre) {
params = rule._pre(params)
}
// 净化参数
let purify = { shouldDelete: new Set([]) }
if (rule._purify) {
const _purify = rule._purify
for (const purifyKey in _purify) {
_purify[purifyKey] = new Set(_purify[purifyKey])
}
purify = Object.assign(purify, _purify)
}
if (isPlainObject(rule)) {
for (const key in rule) {
const parser = rule[key]
if (isFn(parser) && internalKeys.indexOf(key) === -1) {
params[key] = parser(params)
} else if (typeof parser === 'string' && internalKeys.indexOf(key) === -1) {
// 直接转换属性名称的删除旧属性名
params[key] = params[parser]
purify.shouldDelete.add(parser)
}
}
} else if (isFn(rule)) {
params = rule(params)
}
if (purify.shouldDelete) {
for (const item of purify.shouldDelete) {
delete params[item]
}
}
// 转换之后的处理
if (rule._post) {
params = rule._post(params)
}
return params
}
function createApi (ApiClass, options) {
const apiInstance = new ApiClass(options)
return new Proxy(apiInstance, {
get: function (obj, prop) {
if (typeof obj[prop] === 'function' && prop.indexOf('_') !== 0 && obj._protocols && obj._protocols[prop]) {
const protocol = obj._protocols[prop]
return async function (params) {
params = parseParams(params, protocol.args)
let result = await obj[prop](params)
result = parseParams(result, protocol.returnValue)
return result
}
} else {
return obj[prop]
}
}
})
}
module.exports = createApi

View File

@ -0,0 +1,111 @@
const {
callWxOpenApi,
buildUrl
} = require('../normalize')
module.exports = class Auth {
constructor (options) {
this.options = Object.assign({
baseUrl: 'https://api.weixin.qq.com',
timeout: 5000
}, options)
}
async _requestWxOpenapi ({ name, url, data, options }) {
const defaultOptions = {
method: 'GET',
dataType: 'json',
dataAsQueryString: true,
timeout: this.options.timeout
}
const result = await callWxOpenApi({
name: `auth.${name}`,
url: `${this.options.baseUrl}${buildUrl(url, data)}`,
data,
options,
defaultOptions
})
return result
}
async code2Session (code) {
const url = '/sns/jscode2session'
const result = await this._requestWxOpenapi({
name: 'code2Session',
url,
data: {
grant_type: 'authorization_code',
appid: this.options.appId,
secret: this.options.secret,
js_code: code
}
})
return result
}
async getOauthAccessToken (code) {
const url = '/sns/oauth2/access_token'
const result = await this._requestWxOpenapi({
name: 'getOauthAccessToken',
url,
data: {
grant_type: 'authorization_code',
appid: this.options.appId,
secret: this.options.secret,
code
}
})
if (result.expiresIn) {
result.expired = Date.now() + result.expiresIn * 1000
// delete result.expiresIn
}
return result
}
async getUserInfo ({
accessToken,
openid
} = {}) {
const url = '/sns/userinfo'
const {
nickname,
headimgurl: avatar
} = await this._requestWxOpenapi({
name: 'getUserInfo',
url,
data: {
accessToken,
openid,
appid: this.options.appId,
secret: this.options.secret,
scope: 'snsapi_userinfo'
}
})
return {
nickname,
avatar
}
}
async getPhoneNumber (accessToken, code) {
const url = `/wxa/business/getuserphonenumber?access_token=${accessToken}`
const { phoneInfo } = await this._requestWxOpenapi({
name: 'getPhoneNumber',
url,
data: {
code
},
options: {
method: 'POST',
dataAsQueryString: false,
headers: {
'content-type': 'application/json'
}
}
})
return {
purePhoneNumber: phoneInfo.purePhoneNumber
}
}
}

View File

@ -0,0 +1,95 @@
const {
UniCloudError
} = require('../../../common/error')
const {
camel2snakeJson, snake2camelJson
} = require('../../../common/utils')
function generateApiResult (apiName, data) {
if (data.errcode) {
throw new UniCloudError({
code: data.errcode || -2,
message: data.errmsg || `${apiName} fail`
})
} else {
delete data.errcode
delete data.errmsg
return {
...data,
errMsg: `${apiName} ok`,
errCode: 0
}
}
}
function nomalizeError (apiName, error) {
throw new UniCloudError({
code: error.code || -2,
message: error.message || `${apiName} fail`
})
}
// 微信openapi接口接收蛇形snake case参数返回蛇形参数这里进行转化如果是formdata里面的参数需要在对应api实现时就转为蛇形
async function callWxOpenApi ({
name,
url,
data,
options,
defaultOptions
}) {
let result = {}
// 获取二维码的接口wxacode.get和wxacode.getUnlimited不可以传入access_token可能有其他接口也不可以否则会返回data format error
const dataCopy = camel2snakeJson(Object.assign({}, data))
if (dataCopy && dataCopy.access_token) {
delete dataCopy.access_token
}
try {
options = Object.assign({}, defaultOptions, options, { data: dataCopy })
result = await uniCloud.httpclient.request(url, options)
} catch (e) {
return nomalizeError(name, e)
}
// 有几个接口成功返回buffer失败返回json对这些接口统一成返回buffer然后分别解析
let resData = result.data
const contentType = result.headers['content-type']
if (
Buffer.isBuffer(resData) &&
(contentType.indexOf('text/plain') === 0 ||
contentType.indexOf('application/json') === 0)
) {
try {
resData = JSON.parse(resData.toString())
} catch (e) {
resData = resData.toString()
}
} else if (Buffer.isBuffer(resData)) {
resData = {
buffer: resData,
contentType
}
}
return snake2camelJson(
generateApiResult(
name,
resData || {
errCode: -2,
errMsg: 'Request failed'
}
)
)
}
function buildUrl (url, data) {
let query = ''
if (data && data.accessToken) {
const divider = url.indexOf('?') > -1 ? '&' : '?'
query = `${divider}access_token=${data.accessToken}`
}
return `${url}${query}`
}
module.exports = {
callWxOpenApi,
buildUrl
}

View File

@ -0,0 +1,87 @@
const crypto = require('crypto')
const {
isPlainObject
} = require('../../../common/utils')
// 退款通知解密key=md5(key)
function decryptData (encryptedData, key, iv = '') {
// 解密
const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv)
// 设置自动 padding 为 true删除填充补位
decipher.setAutoPadding(true)
let decoded = decipher.update(encryptedData, 'base64', 'utf8')
decoded += decipher.final('utf8')
return decoded
}
function md5 (str, encoding = 'utf8') {
return crypto
.createHash('md5')
.update(str, encoding)
.digest('hex')
}
function sha256 (str, key, encoding = 'utf8') {
return crypto
.createHmac('sha256', key)
.update(str, encoding)
.digest('hex')
}
function getSignStr (obj) {
return Object.keys(obj)
.filter(key => key !== 'sign' && obj[key] !== undefined && obj[key] !== '')
.sort()
.map(key => key + '=' + obj[key])
.join('&')
}
function getNonceStr (length = 16) {
let str = ''
while (str.length < length) {
str += Math.random().toString(32).substring(2)
}
return str.substring(0, length)
}
// 简易版Object转XML只可在微信支付时使用不支持嵌套
function buildXML (obj, rootName = 'xml') {
const content = Object.keys(obj).map(item => {
if (isPlainObject(obj[item])) {
return `<${item}><![CDATA[${JSON.stringify(obj[item])}]]></${item}>`
} else {
return `<${item}><![CDATA[${obj[item]}]]></${item}>`
}
})
return `<${rootName}>${content.join('')}</${rootName}>`
}
function isXML (str) {
const reg = /^(<\?xml.*\?>)?(\r?\n)*<xml>(.|\r?\n)*<\/xml>$/i
return reg.test(str.trim())
};
// 简易版XML转Object只可在微信支付时使用不支持嵌套
function parseXML (xml) {
const xmlReg = /<(?:xml|root).*?>([\s|\S]*)<\/(?:xml|root)>/
const str = xmlReg.exec(xml)[1]
const obj = {}
const nodeReg = /<(.*?)>(?:<!\[CDATA\[){0,1}(.*?)(?:\]\]>){0,1}<\/.*?>/g
let matches = null
// eslint-disable-next-line no-cond-assign
while ((matches = nodeReg.exec(str))) {
obj[matches[1]] = matches[2]
}
return obj
}
module.exports = {
decryptData,
md5,
sha256,
getSignStr,
getNonceStr,
buildXML,
parseXML,
isXML
}