1 |
|
2 | import url from 'url'
|
3 | import { ECPair, address, crypto } from 'bitcoinjs-lib'
|
4 | import { config } from './config'
|
5 | import { Logger } from './logger'
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | export const BLOCKSTACK_HANDLER = 'blockstack'
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | export function nextYear() {
|
18 | return new Date(
|
19 | new Date().setFullYear(
|
20 | new Date().getFullYear() + 1
|
21 | )
|
22 | )
|
23 | }
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 | export function nextMonth() {
|
31 | return new Date(
|
32 | new Date().setMonth(
|
33 | new Date().getMonth() + 1
|
34 | )
|
35 | )
|
36 | }
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 | export function nextHour() {
|
44 | return new Date(
|
45 | new Date().setHours(
|
46 | new Date().getHours() + 1
|
47 | )
|
48 | )
|
49 | }
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 | export function updateQueryStringParameter(uri: string, key: string, value: string) {
|
58 | const re = new RegExp(`([?&])${key}=.*?(&|$)`, 'i')
|
59 | const separator = uri.indexOf('?') !== -1 ? '&' : '?'
|
60 | if (uri.match(re)) {
|
61 | return uri.replace(re, `$1${key}=${value}$2`)
|
62 | } else {
|
63 | return `${uri}${separator}${key}=${value}`
|
64 | }
|
65 | }
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 | export function isLaterVersion(v1: string, v2: string) {
|
77 | if (v1 === undefined) {
|
78 | v1 = '0.0.0'
|
79 | }
|
80 |
|
81 | if (v2 === undefined) {
|
82 | v2 = '0.0.0'
|
83 | }
|
84 |
|
85 | const v1tuple = v1.split('.').map(x => parseInt(x, 10))
|
86 | const v2tuple = v2.split('.').map(x => parseInt(x, 10))
|
87 |
|
88 | for (let index = 0; index < v2.length; index++) {
|
89 | if (index >= v1.length) {
|
90 | v2tuple.push(0)
|
91 | }
|
92 | if (v1tuple[index] < v2tuple[index]) {
|
93 | return false
|
94 | }
|
95 | }
|
96 | return true
|
97 | }
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 | export function hexStringToECPair(skHex: string): ECPair.ECPairInterface {
|
105 | const ecPairOptions = {
|
106 | network: config.network.layer1,
|
107 | compressed: true
|
108 | }
|
109 |
|
110 | if (skHex.length === 66) {
|
111 | if (skHex.slice(64) !== '01') {
|
112 | throw new Error('Improperly formatted private-key hex string. 66-length hex usually '
|
113 | + 'indicates compressed key, but last byte must be == 1')
|
114 | }
|
115 | return ECPair.fromPrivateKey(Buffer.from(skHex.slice(0, 64), 'hex'), ecPairOptions)
|
116 | } else if (skHex.length === 64) {
|
117 | ecPairOptions.compressed = false
|
118 | return ECPair.fromPrivateKey(Buffer.from(skHex, 'hex'), ecPairOptions)
|
119 | } else {
|
120 | throw new Error('Improperly formatted private-key hex string: length should be 64 or 66.')
|
121 | }
|
122 | }
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 | export function ecPairToHexString(secretKey: ECPair.ECPairInterface) {
|
129 | const ecPointHex = secretKey.privateKey.toString('hex')
|
130 | if (secretKey.compressed) {
|
131 | return `${ecPointHex}01`
|
132 | } else {
|
133 | return ecPointHex
|
134 | }
|
135 | }
|
136 |
|
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 | export function ecPairToAddress(keyPair: ECPair.ECPairInterface) {
|
143 | return address.toBase58Check(crypto.hash160(keyPair.publicKey), keyPair.network.pubKeyHash)
|
144 | }
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 | export function makeUUID4() {
|
152 | let d = new Date().getTime()
|
153 | if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
|
154 | d += performance.now()
|
155 | }
|
156 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
157 | const r = (d + Math.random() * 16) % 16 | 0
|
158 | d = Math.floor(d / 16)
|
159 | return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16)
|
160 | })
|
161 | }
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 | export function isSameOriginAbsoluteUrl(uri1: string, uri2: string) {
|
172 | const parsedUri1 = url.parse(uri1)
|
173 | const parsedUri2 = url.parse(uri2)
|
174 |
|
175 | const port1 = parseInt(parsedUri1.port || '0', 10) | 0 || (parsedUri1.protocol === 'https:' ? 443 : 80)
|
176 | const port2 = parseInt(parsedUri2.port || '0', 10) | 0 || (parsedUri2.protocol === 'https:' ? 443 : 80)
|
177 |
|
178 | const match = {
|
179 | scheme: parsedUri1.protocol === parsedUri2.protocol,
|
180 | hostname: parsedUri1.hostname === parsedUri2.hostname,
|
181 | port: port1 === port2,
|
182 | absolute: (uri1.includes('http://') || uri1.includes('https://'))
|
183 | && (uri2.includes('http://') || uri2.includes('https://'))
|
184 | }
|
185 |
|
186 | return match.scheme && match.hostname && match.port && match.absolute
|
187 | }
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 | function getGlobalScope(): Window {
|
200 | if (typeof self !== 'undefined') {
|
201 | return self
|
202 | }
|
203 | if (typeof window !== 'undefined') {
|
204 | return window
|
205 | }
|
206 |
|
207 |
|
208 |
|
209 |
|
210 | if (typeof global !== 'undefined') {
|
211 | return (global as unknown) as Window
|
212 | }
|
213 | throw new Error('Unexpected runtime environment - no supported global scope (`window`, `self`, `global`) available')
|
214 | }
|
215 |
|
216 |
|
217 | function getAPIUsageErrorMessage(
|
218 | scopeObject: unknown,
|
219 | apiName: string,
|
220 | usageDesc?: string
|
221 | ): string {
|
222 | if (usageDesc) {
|
223 | return `Use of '${usageDesc}' requires \`${apiName}\` which is unavailable on the '${scopeObject}' object within the currently executing environment.`
|
224 | } else {
|
225 | return `\`${apiName}\` is unavailable on the '${scopeObject}' object within the currently executing environment.`
|
226 | }
|
227 | }
|
228 |
|
229 | interface GetGlobalObjectOptions {
|
230 | |
231 |
|
232 |
|
233 |
|
234 | throwIfUnavailable?: boolean;
|
235 | |
236 |
|
237 |
|
238 | usageDesc?: string;
|
239 | |
240 |
|
241 |
|
242 |
|
243 |
|
244 | returnEmptyObject?: boolean;
|
245 | }
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 | export function getGlobalObject<K extends keyof Window>(
|
256 | name: K,
|
257 | { throwIfUnavailable, usageDesc, returnEmptyObject }: GetGlobalObjectOptions = { }
|
258 | ): Window[K] {
|
259 | let globalScope: Window
|
260 | try {
|
261 | globalScope = getGlobalScope()
|
262 | if (globalScope) {
|
263 | const obj = globalScope[name]
|
264 | if (obj) {
|
265 | return obj
|
266 | }
|
267 | }
|
268 | } catch (error) {
|
269 | Logger.error(`Error getting object '${name}' from global scope '${globalScope}': ${error}`)
|
270 | }
|
271 | if (throwIfUnavailable) {
|
272 | const errMsg = getAPIUsageErrorMessage(globalScope, name, usageDesc)
|
273 | Logger.error(errMsg)
|
274 | throw new Error(errMsg)
|
275 | }
|
276 | if (returnEmptyObject) {
|
277 | return {}
|
278 | }
|
279 | return undefined
|
280 | }
|
281 |
|
282 |
|
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 | export function getGlobalObjects<K extends keyof Window>(
|
291 | names: K[],
|
292 | { throwIfUnavailable, usageDesc, returnEmptyObject }: GetGlobalObjectOptions = {}
|
293 | ): Pick<Window, K> {
|
294 | let globalScope: Window
|
295 | try {
|
296 | globalScope = getGlobalScope()
|
297 | } catch (error) {
|
298 | Logger.error(`Error getting global scope: ${error}`)
|
299 | if (throwIfUnavailable) {
|
300 | const errMsg = getAPIUsageErrorMessage(globalScope, names[0], usageDesc)
|
301 | Logger.error(errMsg)
|
302 | throw errMsg
|
303 | } else if (returnEmptyObject) {
|
304 | globalScope = {} as any
|
305 | }
|
306 | }
|
307 |
|
308 | const result: Pick<Window, K> = {} as any
|
309 | for (let i = 0; i < names.length; i++) {
|
310 | const name = names[i]
|
311 | try {
|
312 | if (globalScope) {
|
313 | const obj = globalScope[name]
|
314 | if (obj) {
|
315 | result[name] = obj
|
316 | } else if (throwIfUnavailable) {
|
317 | const errMsg = getAPIUsageErrorMessage(globalScope, name, usageDesc)
|
318 | Logger.error(errMsg)
|
319 | throw new Error(errMsg)
|
320 | } else if (returnEmptyObject) {
|
321 | result[name] = {}
|
322 | }
|
323 | }
|
324 | } catch (error) {
|
325 | if (throwIfUnavailable) {
|
326 | const errMsg = getAPIUsageErrorMessage(globalScope, name, usageDesc)
|
327 | Logger.error(errMsg)
|
328 | throw new Error(errMsg)
|
329 | }
|
330 | }
|
331 | }
|
332 | return result
|
333 | }
|