@xyo-network/http-call-witness 3.0.4

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 (126) hide show
  1. package/LICENSE +165 -0
  2. package/README.md +13 -0
  3. package/dist/neutral/Payload/Schema.d.ts +5 -0
  4. package/dist/neutral/Payload/Schema.d.ts.map +1 -0
  5. package/dist/neutral/Payload/identity/asHttpCall.d.ts +5 -0
  6. package/dist/neutral/Payload/identity/asHttpCall.d.ts.map +1 -0
  7. package/dist/neutral/Payload/identity/asHttpCallBase64Result.d.ts +23 -0
  8. package/dist/neutral/Payload/identity/asHttpCallBase64Result.d.ts.map +1 -0
  9. package/dist/neutral/Payload/identity/asHttpCallResult.d.ts +5 -0
  10. package/dist/neutral/Payload/identity/asHttpCallResult.d.ts.map +1 -0
  11. package/dist/neutral/Payload/identity/asHttpCallXmlResult.d.ts +23 -0
  12. package/dist/neutral/Payload/identity/asHttpCallXmlResult.d.ts.map +1 -0
  13. package/dist/neutral/Payload/identity/asHttpUriCall.d.ts +17 -0
  14. package/dist/neutral/Payload/identity/asHttpUriCall.d.ts.map +1 -0
  15. package/dist/neutral/Payload/identity/asHttpUriTemplateCall.d.ts +20 -0
  16. package/dist/neutral/Payload/identity/asHttpUriTemplateCall.d.ts.map +1 -0
  17. package/dist/neutral/Payload/identity/index.d.ts +14 -0
  18. package/dist/neutral/Payload/identity/index.d.ts.map +1 -0
  19. package/dist/neutral/Payload/identity/isHttpCall.d.ts +3 -0
  20. package/dist/neutral/Payload/identity/isHttpCall.d.ts.map +1 -0
  21. package/dist/neutral/Payload/identity/isHttpCallBase64Result.d.ts +3 -0
  22. package/dist/neutral/Payload/identity/isHttpCallBase64Result.d.ts.map +1 -0
  23. package/dist/neutral/Payload/identity/isHttpCallErrorResult.d.ts +3 -0
  24. package/dist/neutral/Payload/identity/isHttpCallErrorResult.d.ts.map +1 -0
  25. package/dist/neutral/Payload/identity/isHttpCallResult.d.ts +3 -0
  26. package/dist/neutral/Payload/identity/isHttpCallResult.d.ts.map +1 -0
  27. package/dist/neutral/Payload/identity/isHttpCallXmlResult.d.ts +3 -0
  28. package/dist/neutral/Payload/identity/isHttpCallXmlResult.d.ts.map +1 -0
  29. package/dist/neutral/Payload/identity/isHttpUriCall.d.ts +3 -0
  30. package/dist/neutral/Payload/identity/isHttpUriCall.d.ts.map +1 -0
  31. package/dist/neutral/Payload/identity/isHttpUriTemplateCall.d.ts +3 -0
  32. package/dist/neutral/Payload/identity/isHttpUriTemplateCall.d.ts.map +1 -0
  33. package/dist/neutral/Payload/index.d.ts +4 -0
  34. package/dist/neutral/Payload/index.d.ts.map +1 -0
  35. package/dist/neutral/Payload/types/HttpCall.d.ts +4 -0
  36. package/dist/neutral/Payload/types/HttpCall.d.ts.map +1 -0
  37. package/dist/neutral/Payload/types/HttpCallBase64Result.d.ts +11 -0
  38. package/dist/neutral/Payload/types/HttpCallBase64Result.d.ts.map +1 -0
  39. package/dist/neutral/Payload/types/HttpCallErrorResult.d.ts +9 -0
  40. package/dist/neutral/Payload/types/HttpCallErrorResult.d.ts.map +1 -0
  41. package/dist/neutral/Payload/types/HttpCallFields.d.ts +8 -0
  42. package/dist/neutral/Payload/types/HttpCallFields.d.ts.map +1 -0
  43. package/dist/neutral/Payload/types/HttpCallHtmlResult.d.ts +9 -0
  44. package/dist/neutral/Payload/types/HttpCallHtmlResult.d.ts.map +1 -0
  45. package/dist/neutral/Payload/types/HttpCallJsonResult.d.ts +34 -0
  46. package/dist/neutral/Payload/types/HttpCallJsonResult.d.ts.map +1 -0
  47. package/dist/neutral/Payload/types/HttpCallResult.d.ts +8 -0
  48. package/dist/neutral/Payload/types/HttpCallResult.d.ts.map +1 -0
  49. package/dist/neutral/Payload/types/HttpCallXmlResult.d.ts +9 -0
  50. package/dist/neutral/Payload/types/HttpCallXmlResult.d.ts.map +1 -0
  51. package/dist/neutral/Payload/types/HttpMeta.d.ts +5 -0
  52. package/dist/neutral/Payload/types/HttpMeta.d.ts.map +1 -0
  53. package/dist/neutral/Payload/types/HttpUriCall.d.ts +7 -0
  54. package/dist/neutral/Payload/types/HttpUriCall.d.ts.map +1 -0
  55. package/dist/neutral/Payload/types/HttpUriTemplateCall.d.ts +8 -0
  56. package/dist/neutral/Payload/types/HttpUriTemplateCall.d.ts.map +1 -0
  57. package/dist/neutral/Payload/types/MimeTypes.d.ts +2 -0
  58. package/dist/neutral/Payload/types/MimeTypes.d.ts.map +1 -0
  59. package/dist/neutral/Payload/types/Queries.d.ts +2 -0
  60. package/dist/neutral/Payload/types/Queries.d.ts.map +1 -0
  61. package/dist/neutral/Payload/types/Verb.d.ts +2 -0
  62. package/dist/neutral/Payload/types/Verb.d.ts.map +1 -0
  63. package/dist/neutral/Payload/types/index.d.ts +15 -0
  64. package/dist/neutral/Payload/types/index.d.ts.map +1 -0
  65. package/dist/neutral/Witness/Config.d.ts +140 -0
  66. package/dist/neutral/Witness/Config.d.ts.map +1 -0
  67. package/dist/neutral/Witness/Params.d.ts +7 -0
  68. package/dist/neutral/Witness/Params.d.ts.map +1 -0
  69. package/dist/neutral/Witness/Schema.d.ts +3 -0
  70. package/dist/neutral/Witness/Schema.d.ts.map +1 -0
  71. package/dist/neutral/Witness/Witness.d.ts +16 -0
  72. package/dist/neutral/Witness/Witness.d.ts.map +1 -0
  73. package/dist/neutral/Witness/index.d.ts +5 -0
  74. package/dist/neutral/Witness/index.d.ts.map +1 -0
  75. package/dist/neutral/index.d.ts +3 -0
  76. package/dist/neutral/index.d.ts.map +1 -0
  77. package/dist/neutral/index.mjs +349 -0
  78. package/dist/neutral/index.mjs.map +1 -0
  79. package/dist/neutral/lib/checkIpfsUrl.d.ts +2 -0
  80. package/dist/neutral/lib/checkIpfsUrl.d.ts.map +1 -0
  81. package/dist/neutral/lib/index.d.ts +2 -0
  82. package/dist/neutral/lib/index.d.ts.map +1 -0
  83. package/package.json +62 -0
  84. package/src/Payload/Schema.ts +5 -0
  85. package/src/Payload/identity/asHttpCall.ts +5 -0
  86. package/src/Payload/identity/asHttpCallBase64Result.ts +5 -0
  87. package/src/Payload/identity/asHttpCallResult.ts +5 -0
  88. package/src/Payload/identity/asHttpCallXmlResult.ts +5 -0
  89. package/src/Payload/identity/asHttpUriCall.ts +5 -0
  90. package/src/Payload/identity/asHttpUriTemplateCall.ts +5 -0
  91. package/src/Payload/identity/index.ts +13 -0
  92. package/src/Payload/identity/isHttpCall.ts +6 -0
  93. package/src/Payload/identity/isHttpCallBase64Result.ts +8 -0
  94. package/src/Payload/identity/isHttpCallErrorResult.ts +7 -0
  95. package/src/Payload/identity/isHttpCallResult.ts +6 -0
  96. package/src/Payload/identity/isHttpCallXmlResult.ts +8 -0
  97. package/src/Payload/identity/isHttpUriCall.ts +4 -0
  98. package/src/Payload/identity/isHttpUriTemplateCall.ts +5 -0
  99. package/src/Payload/index.ts +3 -0
  100. package/src/Payload/types/HttpCall.ts +4 -0
  101. package/src/Payload/types/HttpCallBase64Result.ts +15 -0
  102. package/src/Payload/types/HttpCallErrorResult.ts +13 -0
  103. package/src/Payload/types/HttpCallFields.ts +8 -0
  104. package/src/Payload/types/HttpCallHtmlResult.ts +13 -0
  105. package/src/Payload/types/HttpCallJsonResult.ts +23 -0
  106. package/src/Payload/types/HttpCallResult.ts +12 -0
  107. package/src/Payload/types/HttpCallXmlResult.ts +13 -0
  108. package/src/Payload/types/HttpMeta.ts +4 -0
  109. package/src/Payload/types/HttpUriCall.ts +11 -0
  110. package/src/Payload/types/HttpUriTemplateCall.ts +12 -0
  111. package/src/Payload/types/MimeTypes.ts +13 -0
  112. package/src/Payload/types/Queries.ts +1 -0
  113. package/src/Payload/types/Verb.ts +1 -0
  114. package/src/Payload/types/index.ts +14 -0
  115. package/src/Witness/Config.ts +43 -0
  116. package/src/Witness/Params.ts +11 -0
  117. package/src/Witness/Schema.ts +2 -0
  118. package/src/Witness/Witness.ts +219 -0
  119. package/src/Witness/index.ts +4 -0
  120. package/src/Witness/spec/opensea.nft-call.json +41 -0
  121. package/src/index.ts +2 -0
  122. package/src/lib/checkIpfsUrl.ts +44 -0
  123. package/src/lib/index.ts +1 -0
  124. package/src/types.d.ts +1 -0
  125. package/typedoc.json +5 -0
  126. package/xy.config.ts +10 -0
@@ -0,0 +1,219 @@
1
+ /* eslint-disable max-statements */
2
+ import { assertEx } from '@xylabs/assert'
3
+ import { AxiosJson } from '@xylabs/axios'
4
+ import type { Hash } from '@xylabs/hex'
5
+ import { URL } from '@xylabs/url'
6
+ import { AbstractWitness } from '@xyo-network/abstract-witness'
7
+ import { PayloadHasher } from '@xyo-network/hash'
8
+ import { PayloadBuilder } from '@xyo-network/payload-builder'
9
+ import type { Schema } from '@xyo-network/payload-model'
10
+ import { isPayloadOfSchemaType } from '@xyo-network/payload-model'
11
+ import type { AxiosError } from 'axios'
12
+ import { Axios } from 'axios'
13
+ import { fromByteArray } from 'base64-js'
14
+ import fillTemplate from 'es6-dynamic-template'
15
+
16
+ import { checkIpfsUrl } from '../lib/index.ts'
17
+ import type {
18
+ HttpCall,
19
+ HttpCallBase64Result,
20
+ HttpCallErrorResult,
21
+ HttpCallJsonResult,
22
+ HttpCallJsonResultType,
23
+ HttpCallResult,
24
+ HttpCallXmlResult,
25
+ MimeTypes,
26
+ } from '../Payload/index.ts'
27
+ import {
28
+ asHttpUriCall,
29
+ asHttpUriTemplateCall,
30
+ HttpCallResultSchema,
31
+ HttpCallSchema,
32
+ } from '../Payload/index.ts'
33
+ import type { HttpCallHtmlResult } from '../Payload/types/index.ts'
34
+ import { asHttpUriCallWitnessConfig, asHttpUriTemplateCallWitnessConfig } from './Config.ts'
35
+ import type { HttpCallWitnessParams } from './Params.ts'
36
+ import { HttpCallWitnessConfigSchema } from './Schema.ts'
37
+
38
+ export class HttpCallWitness<TParams extends HttpCallWitnessParams = HttpCallWitnessParams> extends AbstractWitness<TParams, HttpCall, HttpCallResult> {
39
+ static override readonly configSchemas: Schema[] = [...super.configSchemas, HttpCallWitnessConfigSchema]
40
+ static override readonly defaultConfigSchema: Schema = HttpCallWitnessConfigSchema
41
+
42
+ get accept(): MimeTypes {
43
+ return this.config.accept ?? 'application/json'
44
+ }
45
+
46
+ get ipfsGateway() {
47
+ return this.params.ipfsGateway
48
+ }
49
+
50
+ get timeout() {
51
+ return this.config.timeout
52
+ }
53
+
54
+ getFullUri(call?: HttpCall): string {
55
+ const { uri: callUri } = asHttpUriCall(call) ?? {}
56
+ const {
57
+ uriTemplate: callUriTemplate, params: callParams, queries: callQueries,
58
+ } = asHttpUriTemplateCall(call) ?? {}
59
+ const { uri: configUri } = asHttpUriCallWitnessConfig(this.config) ?? {}
60
+ const {
61
+ uriTemplate: configUriTemplate, params: configParams, queries: configQueries,
62
+ } = asHttpUriTemplateCallWitnessConfig(this.config) ?? {}
63
+
64
+ const params = { ...configParams, ...callParams }
65
+
66
+ let url: URL | undefined = undefined
67
+
68
+ if (callUri) {
69
+ url = new URL(callUri)
70
+ } else if (callUriTemplate) {
71
+ url = new URL(fillTemplate(callUriTemplate, params))
72
+ } else if (configUri) {
73
+ url = new URL(configUri)
74
+ } else if (configUriTemplate) {
75
+ url = new URL(fillTemplate(configUriTemplate, params))
76
+ }
77
+
78
+ if (url) {
79
+ const queries = Object.entries({ ...configQueries, ...callQueries })
80
+ queries.map(([key, value]) => url?.searchParams.set(key, value))
81
+ return url.href
82
+ }
83
+
84
+ throw new Error('Unable to determine uri. No uri/uriTemplate specified in either the call or config.')
85
+ }
86
+
87
+ getHeaders(headers?: Record<string, string | undefined>): Record<string, string | undefined> {
88
+ return {
89
+ ...this.params.headers, ...this.config.headers, ...headers,
90
+ }
91
+ }
92
+
93
+ protected override async observeHandler(inPayloads: HttpCall[] = []): Promise<HttpCallResult[]> {
94
+ await this.started('throw')
95
+ try {
96
+ const observations = await Promise.all(
97
+ inPayloads.filter(isPayloadOfSchemaType(HttpCallSchema)).map(async (call) => {
98
+ const { verb: callVerb } = call
99
+ const { verb: configVerb } = this.config
100
+ const verb = callVerb ?? configVerb ?? 'get'
101
+ const uri = this.getFullUri(call)
102
+
103
+ const validatedUri = assertEx(checkIpfsUrl(uri, this.ipfsGateway), () => 'Invalid URI')
104
+
105
+ if (verb === 'get') {
106
+ return this.httpGet(validatedUri, (await PayloadBuilder.build(call)).$hash)
107
+ }
108
+
109
+ const observation: HttpCallResult = {
110
+ call: await PayloadHasher.hash(call),
111
+ schema: HttpCallResultSchema,
112
+ }
113
+ return observation
114
+ }),
115
+ )
116
+ return observations
117
+ } catch (ex) {
118
+ const error = ex as Error
119
+ console.error(`Error [${this.config.name}]: ${error.message}`)
120
+ console.log(error.stack)
121
+ throw error
122
+ }
123
+ }
124
+
125
+ // eslint-disable-next-line complexity
126
+ private async httpGet(url: string, call: Hash, headers?: Record<string, string | undefined>): Promise<HttpCallResult> {
127
+ const result: HttpCallResult = {
128
+ call,
129
+ schema: HttpCallResultSchema,
130
+ }
131
+ try {
132
+ switch (this.accept) {
133
+ case 'application/json': {
134
+ const axios = new AxiosJson({
135
+ headers: { ...this.getHeaders(headers), Accept: 'application/json' }, timeout: this.timeout, decompress: true,
136
+ })
137
+ const response = await axios.get<HttpCallJsonResultType>(url)
138
+ if (response.status >= 200 && response.status < 300) {
139
+ const jsonResult = result as HttpCallJsonResult
140
+ jsonResult.data = response.data
141
+ jsonResult.contentType = 'application/json'
142
+ } else {
143
+ const errorResult = result as HttpCallErrorResult
144
+ errorResult.http = { status: response.status }
145
+ }
146
+ break
147
+ }
148
+ case 'application/xml':
149
+ case 'text/xml': {
150
+ const axios = new Axios({
151
+ headers: { ...this.getHeaders(headers), Accept: this.accept },
152
+ responseType: 'arraybuffer',
153
+ decompress: true,
154
+ timeout: this.timeout,
155
+ })
156
+ const response = await axios.get(url)
157
+ if (response.status >= 200 && response.status < 300) {
158
+ const xmlResult = result as HttpCallXmlResult
159
+ xmlResult.data = Buffer.from(response.data, 'binary').toString('utf8')
160
+ xmlResult.contentType = response.headers['content-type']?.toString() ?? 'application/xml'
161
+ } else {
162
+ const errorResult = result as HttpCallErrorResult
163
+ errorResult.http = { status: response.status }
164
+ }
165
+ break
166
+ }
167
+ case 'text/html':{
168
+ const axios = new Axios({
169
+ headers: { ...this.getHeaders(headers), Accept: this.accept },
170
+ responseType: 'text',
171
+ decompress: true,
172
+ timeout: this.timeout,
173
+ })
174
+ const response = await axios.get(url)
175
+ if (response.status >= 200 && response.status < 300) {
176
+ const htmlResult = result as HttpCallHtmlResult
177
+ htmlResult.data = response.data
178
+ htmlResult.contentType = response.headers['content-type']?.toString() ?? 'text/html'
179
+ } else {
180
+ const errorResult = result as HttpCallErrorResult
181
+ errorResult.http = { status: response.status }
182
+ }
183
+ break
184
+ }
185
+ default: {
186
+ const axios = new Axios({
187
+ headers: this.params.headers, responseType: 'arraybuffer', timeout: this.timeout, decompress: true,
188
+ })
189
+ const response = await axios.get(url)
190
+ if (response.status >= 200 && response.status < 300) {
191
+ const jsonResult = result as HttpCallBase64Result
192
+ jsonResult.data = fromByteArray(Buffer.from(response.data, 'binary'))
193
+ jsonResult.contentType = response.headers['content-type']?.toString() ?? 'application/octet-stream'
194
+ } else {
195
+ const errorResult = result as HttpCallErrorResult
196
+ errorResult.http = { status: response.status }
197
+ }
198
+ break
199
+ }
200
+ }
201
+ } catch (ex) {
202
+ const axiosError = ex as AxiosError
203
+ if (axiosError.isAxiosError) {
204
+ if (axiosError?.response?.status !== undefined) {
205
+ result.http = result.http ?? {}
206
+ result.http.status = axiosError?.response?.status
207
+ }
208
+ if (axiosError?.code !== undefined) {
209
+ result.http = result.http ?? {}
210
+ result.http.code = axiosError?.code
211
+ }
212
+ return result
213
+ } else {
214
+ throw ex
215
+ }
216
+ }
217
+ return result
218
+ }
219
+ }
@@ -0,0 +1,4 @@
1
+ export * from './Config.ts'
2
+ export * from './Params.ts'
3
+ export * from './Schema.ts'
4
+ export * from './Witness.ts'
@@ -0,0 +1,41 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/XYOracleNetwork/sdk-xyo-client-js/main/packages/manifest/src/schema.json",
3
+ "nodes": [
4
+ {
5
+ "config": {
6
+ "name": "ApiCall",
7
+ "schema": "network.xyo.node.config"
8
+ },
9
+ "modules": {
10
+ "public": [
11
+ {
12
+ "config": {
13
+ "language": "javascript",
14
+ "name": "HttpCallWitness",
15
+ "queries": {
16
+ "limit": 1
17
+ },
18
+ "schema": "network.xyo.http.call.witness.config",
19
+ "uriTemplate": "https://api.opensea.io/api/v2/chain/ethereum/account/${address}/nfts"
20
+ }
21
+ },
22
+ {
23
+ "config": {
24
+ "language": "javascript",
25
+ "name": "HttpCallSentinel",
26
+ "schema": "network.xyo.sentinel.config",
27
+ "synchronous": "true",
28
+ "tasks": [
29
+ {
30
+ "input": true,
31
+ "mod": "HttpCallWitness"
32
+ }
33
+ ]
34
+ }
35
+ }
36
+ ]
37
+ }
38
+ }
39
+ ],
40
+ "schema": "network.xyo.manifest"
41
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './Payload/index.ts'
2
+ export * from './Witness/index.ts'
@@ -0,0 +1,44 @@
1
+ import { assertEx } from '@xylabs/assert'
2
+ import { URL } from '@xylabs/url'
3
+
4
+ const allowIpfsIoRepair = true
5
+
6
+ /**
7
+ * Returns the equivalent IPFS gateway URL for the supplied URL.
8
+ * @param urlToCheck The URL to check
9
+ * @returns If the supplied URL is an IPFS URL, it converts the URL to the
10
+ * equivalent IPFS gateway URL. Otherwise, returns the original URL.
11
+ */
12
+ export const checkIpfsUrl = (urlToCheck: string, ipfsGateway?: string): string => {
13
+ try {
14
+ const url = new URL(urlToCheck)
15
+ let protocol = url.protocol
16
+ let host = url.host
17
+ let path = url.pathname
18
+ const query = url.search
19
+ if (protocol === 'ipfs:') {
20
+ protocol = 'https:'
21
+ host = assertEx(ipfsGateway, () => 'No ipfsGateway provided')
22
+ path = url.host === 'ipfs' ? `ipfs${path}` : `ipfs/${url.host}${path}`
23
+ const root = `${protocol}//${host}/${path}`
24
+ return query?.length > 0 ? `${root}?${query}` : root
25
+ } else if (allowIpfsIoRepair && protocol === 'https' && host === 'ipfs.io') {
26
+ protocol = 'https:'
27
+ host = assertEx(ipfsGateway, () => 'No ipfsGateway provided')
28
+ const pathParts = path.split('/')
29
+ if (pathParts[0] === 'ipfs') {
30
+ pathParts.shift()
31
+ }
32
+ path = pathParts.join('/')
33
+ const root = `${protocol}//${host}/${path}`
34
+ return query?.length > 0 ? `${root}?${query}` : root
35
+ } else {
36
+ return urlToCheck
37
+ }
38
+ } catch {
39
+ // const error = ex as Error
40
+ // console.error(`${error.name}:${error.message} [${urlToCheck}]`)
41
+ // console.log(error.stack)
42
+ return urlToCheck
43
+ }
44
+ }
@@ -0,0 +1 @@
1
+ export * from './checkIpfsUrl.ts'
package/src/types.d.ts ADDED
@@ -0,0 +1 @@
1
+ declare module 'es6-dynamic-template'
package/typedoc.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://typedoc.org/schema.json",
3
+ "entryPoints": ["./src/index.ts"],
4
+ "tsconfig": "./tsconfig.typedoc.json"
5
+ }
package/xy.config.ts ADDED
@@ -0,0 +1,10 @@
1
+ import type { XyTsupConfig } from '@xylabs/ts-scripts-yarn3'
2
+ const config: XyTsupConfig = {
3
+ compile: {
4
+ browser: {},
5
+ neutral: { src: true },
6
+ node: {},
7
+ },
8
+ }
9
+
10
+ export default config