-
Notifications
You must be signed in to change notification settings - Fork 2
/
Jsonp.ts
145 lines (124 loc) · 3.92 KB
/
Jsonp.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import { options } from '../utils/types'
import { noop, euc } from '../utils/index'
const PREFIX = 'callback'
export default class Jsonp {
handler: Promise<object> // facade API based on error handler and response handler
private _url: string // converted url based on basic url and other params
private _jsonpCallback: string // response handler
private _reference: Element // reference element
private _script: HTMLScriptElement | null // trigger element
private _timer: number // timer ID
constructor ({
url,
timeout = 6000,
jsonpCallback = `${PREFIX}${Date.now()}`,
callbackParams = 'jsonpCallback',
urlParams = {}
}: options) {
this.checkOptions({
url,
jsonpCallback
})
this.initState({
timeout,
jsonpCallback
})
this.encodeURL({
url,
callbackParams,
urlParams
})
this.insert(this._url)
}
checkOptions ({
url,
jsonpCallback
}: options) {
if (!url) throw new Error('Please check your request url.')
// Every jsonp request will reset global request function named value of
// jsonpCallback, so this value MUST NOT be `jsonp`.
// This checking only works in CDN installing, not as a dependency using
if (jsonpCallback === 'jsonp') throw new Error('Don\'t name jsonpCallback to `jsonp` for unexpected reset. Please use any non-jsonp value')
}
initState ({
timeout,
jsonpCallback
}: {
timeout: options['timeout']
jsonpCallback: options['jsonpCallback']
}) {
this.createScript()
// unique response handler
this._jsonpCallback = jsonpCallback
// keep behind setting this._jsonpCallback
this.handler = this.createHandler()
// set timer for limit request time
this.createTimer(timeout)
}
createScript () {
this._reference = document.getElementsByTagName('script')[0]
|| document.body.lastElementChild
this._script = document.createElement('script')
}
/**
* 1. Request timer will be cleaned when response handler invoked.
* 2. use arrow function to keep `this` keywords value (Jsonp instance).
*/
createHandler () {
return new Promise((resolve, reject) => {
// handle 404/500 in response
this._script.onerror = () => {
this.cleanScript()
reject(new Error(`Countdown has been clear! JSONP request unsuccessfully due to 404/500`))
}
(<any>window)[this._jsonpCallback] = (data: object) => {
this.cleanScript()
resolve(data)
}
})
}
// create a request timer for limiting request period
createTimer (timeout: options['timeout']) {
// It can be disable when param timeout equal falsy value (0, null etc.)
if (timeout) {
this._timer = window.setTimeout(() => {
(<any>window)[this._jsonpCallback] = noop
this._timer = null
this.cleanScript()
throw new Error('JSONP request unsuccessfully (eg.timeout or wrong url).')
}, timeout)
}
}
encodeURL ({
url,
callbackParams,
urlParams
}: options) {
// name of query parameter to specify the callback name
// eg. ?callback=...
const id = euc(this._jsonpCallback)
url += `${url.indexOf('?') < 0 ? '?' : '&'}${callbackParams}=${id}`
// add other parameters to url ending excluding callback name parameter
const keys = Object.keys(urlParams)
keys.forEach(key => {
const value = urlParams[key] !== undefined ? urlParams[key] : ''
url += `&${key}=${euc(value)}`
})
// converted request url
this._url = url
}
// activate JSONP
insert (url: string) {
this._script.src = url
this._reference.parentNode.insertBefore(this._script, this._reference)
}
cleanScript () {
if (this._script.parentNode) {
this._reference.parentNode.removeChild(this._script)
this._script = null
}
// reset response handler
(<any>window)[this._jsonpCallback] = noop
if (this._timer) window.clearTimeout(this._timer)
}
}