UNPKG

9.34 kBPlain TextView Raw
1
2import url from 'url'
3import { ECPair, address, crypto } from 'bitcoinjs-lib'
4import { config } from './config'
5import { Logger } from './logger'
6
7/**
8 * @ignore
9 */
10export const BLOCKSTACK_HANDLER = 'blockstack'
11
12/**
13 * Time
14 * @private
15 * @ignore
16 */
17export function nextYear() {
18 return new Date(
19 new Date().setFullYear(
20 new Date().getFullYear() + 1
21 )
22 )
23}
24
25/**
26 * Time
27 * @private
28 * @ignore
29 */
30export function nextMonth() {
31 return new Date(
32 new Date().setMonth(
33 new Date().getMonth() + 1
34 )
35 )
36}
37
38/**
39 * Time
40 * @private
41 * @ignore
42 */
43export function nextHour() {
44 return new Date(
45 new Date().setHours(
46 new Date().getHours() + 1
47 )
48 )
49}
50
51/**
52 * Query Strings
53 * @private
54 * @ignore
55 */
56
57export 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 * Versioning
69 * @param {string} v1 - the left half of the version inequality
70 * @param {string} v2 - right half of the version inequality
71 * @returns {bool} iff v1 >= v2
72 * @private
73 * @ignore
74 */
75
76export 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 * Time
101 * @private
102 * @ignore
103 */
104export 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 * @ignore
127 */
128export 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 * Time
139 * @private
140 * @ignore
141 */
142export function ecPairToAddress(keyPair: ECPair.ECPairInterface) {
143 return address.toBase58Check(crypto.hash160(keyPair.publicKey), keyPair.network.pubKeyHash)
144}
145
146/**
147 * UUIDs
148 * @private
149 * @ignore
150 */
151export function makeUUID4() {
152 let d = new Date().getTime()
153 if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
154 d += performance.now() // use high-precision timer if available
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 * Checks if both urls pass the same origin check & are absolute
165 * @param {[type]} uri1 first uri to check
166 * @param {[type]} uri2 second uri to check
167 * @return {Boolean} true if they pass the same origin check
168 * @private
169 * @ignore
170 */
171export 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 * Returns the global scope `Window`, `WorkerGlobalScope`, or `NodeJS.Global` if available in the
191 * currently executing environment.
192 * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/self
193 * @see https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/self
194 * @see https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope
195 *
196 * This could be switched to `globalThis` once it is standardized and widely available.
197 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis
198 */
199function getGlobalScope(): Window {
200 if (typeof self !== 'undefined') {
201 return self
202 }
203 if (typeof window !== 'undefined') {
204 return window
205 }
206 // This function is meant to be called when accessing APIs that are typically only available in
207 // web-browser/DOM environments, but we also want to support situations where running in Node.js
208 // environment, and a polyfill was added to the Node.js `global` object scope without adding the
209 // `window` global object as well.
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
217function 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
229interface GetGlobalObjectOptions {
230 /**
231 * Throw an error if the object is not found.
232 * @default false
233 */
234 throwIfUnavailable?: boolean;
235 /**
236 * Additional information to include in an error if thrown.
237 */
238 usageDesc?: string;
239 /**
240 * If the object is not found, return an new empty object instead of undefined.
241 * Requires [[throwIfUnavailable]] to be falsey.
242 * @default false
243 */
244 returnEmptyObject?: boolean;
245}
246
247/**
248 * Returns an object from the global scope (`Window` or `WorkerGlobalScope`) if it
249 * is available within the currently executing environment.
250 * When executing within the Node.js runtime these APIs are unavailable and will be
251 * `undefined` unless the API is provided via polyfill.
252 * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/self
253 * @ignore
254 */
255export 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 * Returns a specified subset of objects from the global scope (`Window` or `WorkerGlobalScope`)
284 * if they are available within the currently executing environment.
285 * When executing within the Node.js runtime these APIs are unavailable will be `undefined`
286 * unless the API is provided via polyfill.
287 * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/self
288 * @ignore
289 */
290export 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}