├── README.md
├── index.js
├── package-lock.json
├── package.json
└── src
├── console.js
├── event.js
├── info.js
├── network.js
└── tool.js
/README.md:
--------------------------------------------------------------------------------
1 | # RNVConsole
2 | vConsole for react native, inspired by vconsole & vconsole-react-native.
3 | Debugger on top of screen. Have a try in expo https://snack.expo.io/SklJHMS3S
4 |
5 | ## Features
6 | 1. console[log, warn, error, info] in Log Panel.
7 | 2. exec Command to show message.
8 | 3. Network request list & detail.
9 | 4. Customized Version Info you want to show.
10 |
11 | ## Install
12 | ```
13 | npm install rnvconsole
14 | ```
15 |
16 | ## Usage
17 | ```javascript
18 | // Options
19 | import RN from 'react-native'
20 | import Native from '../native' // your own Module
21 |
22 | // Show app information in INFO panel
23 | const INFO = {
24 | version: '1.0.0',
25 | test_version: '4',
26 | message: 'test xxx features'
27 | }
28 | const options = {
29 | info: INFO,
30 | // global Object can be called in Command Input
31 | global: {
32 | rn: RN
33 | native: Native
34 | }
35 | }
36 |
37 | const RNVConsole = require('rnvconsole').showLogWhenDev(options)
38 |
39 | // in render function
40 | render() {
41 | return (
42 |
43 | {RNVConsole} // add RNVConsole somewhere in JSX
44 |
45 |
46 | )
47 | }
48 | ```
49 |
50 | ## Examples
51 |
52 | ### Log Panel
53 |

54 |
55 | ### Command
56 | Object defined in global options can be call in command. Such as react-native module or your native function.
57 | 
58 |
59 | ### Network Panel
60 | 
61 |
62 | ### Info Panel
63 | 
64 |
65 | ### Dev Button
66 | 
67 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * vconsole for react native 开发测试工具
3 | */
4 |
5 | import React, { PureComponent } from 'react'
6 | import {
7 | View,
8 | Text,
9 | TouchableOpacity,
10 | PanResponder,
11 | Animated,
12 | Dimensions,
13 | StyleSheet,
14 | TextInput,
15 | Keyboard,
16 | NativeModules,
17 | Platform,
18 | KeyboardAvoidingView
19 | } from 'react-native'
20 | import event from './src/event'
21 | import Network from './src/network'
22 | import Log from './src/console'
23 | import Info from './src/info'
24 |
25 | const PANELS = ['Log', 'Network', 'Info']
26 | const { width, height } = Dimensions.get('window')
27 | let AppInfo = {}
28 | let GlobalCommandObj = {}
29 |
30 | function evalInContext(js, context) {
31 | return function(str) {
32 | let result = ''
33 | try {
34 | // eslint-disable-next-line no-eval
35 | result = eval(str)
36 | } catch (err) {
37 | result = '无效输入'
38 | }
39 | return event.trigger('addLog', result)
40 | }.call(context, `with(this) { ${js} } `)
41 | }
42 |
43 | class RNVConsole extends PureComponent {
44 | constructor(props) {
45 | super(props)
46 | this.state = {
47 | commandValue: '',
48 | showPanel: false,
49 | currentPanelTab: 'Log',
50 | pan: new Animated.ValueXY(),
51 | scale: new Animated.Value(1)
52 | }
53 | this.panResponder = PanResponder.create({
54 | onStartShouldSetPanResponder: () => true,
55 | onPanResponderGrant: () => {
56 | this.state.pan.setOffset({
57 | x: this.state.pan.x._value,
58 | y: this.state.pan.y._value
59 | })
60 | this.state.pan.setValue({ x: 0, y: 0 })
61 | Animated.spring(this.state.scale, { toValue: 1.3, friction: 3 }).start()
62 | },
63 | onPanResponderMove: Animated.event([null, { dx: this.state.pan.x, dy: this.state.pan.y }]),
64 | onPanResponderRelease: (evt, gestureState) => {
65 | // 点击
66 | if (Math.abs(gestureState.dx) < 5 && Math.abs(gestureState.dy) < 5) {
67 | this.togglePanel()
68 | }
69 | this.state.pan.flattenOffset()
70 | Animated.spring(this.state.scale, { toValue: 1, friction: 3 }).start()
71 | }
72 | })
73 | }
74 |
75 | togglePanel = () => {
76 | this.setState(state => ({
77 | showPanel: !state.showPanel
78 | }))
79 | }
80 |
81 | clearLogs = () => {
82 | const tabName = this.state.currentPanelTab
83 | event.trigger('clear', tabName)
84 | }
85 |
86 | showDevPanel = () => {
87 | NativeModules.DevMenu.show()
88 | }
89 |
90 | reload = () => {
91 | NativeModules.DevMenu.reload()
92 | }
93 |
94 | execCommand = () => {
95 | const context = GlobalCommandObj
96 | evalInContext(this.state.commandValue, context)
97 | Keyboard.dismiss()
98 | }
99 |
100 | renderPanelHeader() {
101 | return (
102 |
103 | {PANELS.map(type => (
104 | {
107 | this.setState({
108 | currentPanelTab: type
109 | })
110 | }}
111 | style={[
112 | styles.panelHeaderItem,
113 | type === this.state.currentPanelTab && styles.activeTab
114 | ]}
115 | >
116 | {type}
117 |
118 | ))}
119 |
120 | )
121 | }
122 |
123 | renderCommandBar() {
124 | return (
125 |
126 | this.setState({ commandValue: text })}
131 | value={this.state.commandValue}
132 | />
133 | this.execCommand()}>
134 | OK
135 |
136 |
137 | )
138 | }
139 |
140 | renderPanelFooter() {
141 | return (
142 |
143 |
144 | Clear
145 |
146 |
147 | Dev
148 |
149 |
150 | Hide
151 |
152 |
153 | )
154 | }
155 |
156 | renderEmptyPanel() {
157 | return (
158 |
159 | 敬请期待!
160 |
161 | )
162 | }
163 |
164 | renderPanel() {
165 | const panels = {
166 | Log,
167 | Network,
168 | Info: ,
169 | Empty: this.renderEmptyPanel()
170 | }
171 | return (
172 |
176 | {this.renderPanelHeader()}
177 |
178 | {panels[this.state.currentPanelTab || 'Log'] || panels.Empty}
179 |
180 | {this.renderCommandBar()}
181 | {this.renderPanelFooter()}
182 |
183 | )
184 | }
185 |
186 | renderHomeBtn() {
187 | const { pan, scale } = this.state
188 | const [translateX, translateY] = [pan.x, pan.y]
189 | const btnStyle = { transform: [{ translateX }, { translateY }, { scale }] }
190 |
191 | return (
192 |
193 | RNVConsole
194 |
195 | )
196 | }
197 |
198 | render() {
199 | return this.state.showPanel ? this.renderPanel() : this.renderHomeBtn()
200 | }
201 | }
202 |
203 | const styles = StyleSheet.create({
204 | activeTab: {
205 | backgroundColor: '#fff'
206 | },
207 | panel: {
208 | position: 'absolute',
209 | zIndex: 999999999,
210 | elevation: 999999999,
211 | backgroundColor: '#fff',
212 | width,
213 | height: (height / 3) * 2,
214 | bottom: 80,
215 | right: 0,
216 | flexDirection: 'column'
217 | },
218 | panelHeader: {
219 | width,
220 | backgroundColor: '#eee',
221 | flexDirection: 'row',
222 | borderWidth: StyleSheet.hairlineWidth,
223 | borderColor: '#d9d9d9'
224 | },
225 | panelHeaderItem: {
226 | flex: 1,
227 | height: 40,
228 | color: '#000',
229 | borderRightWidth: StyleSheet.hairlineWidth,
230 | borderColor: '#d9d9d9',
231 | justifyContent: 'center'
232 | },
233 | panelHeaderItemText: {
234 | textAlign: 'center'
235 | },
236 | panelContent: {
237 | width,
238 | flex: 0.9
239 | },
240 | panelBottom: {
241 | width,
242 | flex: 0.1,
243 | borderWidth: StyleSheet.hairlineWidth,
244 | borderColor: '#d9d9d9',
245 | flexDirection: 'row',
246 | alignItems: 'center',
247 | backgroundColor: '#eee'
248 | },
249 | panelBottomBtn: {
250 | flex: 1,
251 | height: 40,
252 | borderRightWidth: StyleSheet.hairlineWidth,
253 | borderColor: '#d9d9d9',
254 | justifyContent: 'center'
255 | },
256 | panelBottomBtnText: {
257 | color: '#000',
258 | fontSize: 14,
259 | textAlign: 'center'
260 | },
261 | panelEmpty: {
262 | flex: 1,
263 | alignItems: 'center',
264 | justifyContent: 'center'
265 | },
266 | homeBtn: {
267 | width: 100,
268 | height: 40,
269 | backgroundColor: '#04be02',
270 | borderRadius: 4,
271 | alignItems: 'center',
272 | justifyContent: 'center',
273 | position: 'absolute',
274 | zIndex: 999999999,
275 | bottom: 140,
276 | right: 10,
277 | shadowColor: 'rgb(18,34,74)',
278 | shadowOffset: { width: 0, height: 1 },
279 | shadowOpacity: 0.08,
280 | elevation: 0.4
281 | },
282 | homeBtnText: {
283 | color: '#fff'
284 | },
285 | commandBar: {
286 | height: 40,
287 | flexDirection: 'row',
288 | borderWidth: StyleSheet.hairlineWidth,
289 | borderColor: '#d9d9d9'
290 | },
291 | commandBarInput: {
292 | flex: 1,
293 | paddingLeft: 10
294 | },
295 | commandBarBtn: {
296 | width: 60,
297 | alignItems: 'center',
298 | justifyContent: 'center',
299 | backgroundColor: '#eee'
300 | }
301 | })
302 |
303 | module.exports = {
304 | Panel: RNVConsole,
305 | showLogWhenDev(options) {
306 | options = options || {}
307 | AppInfo = options.info || ''
308 | GlobalCommandObj = options.global || {}
309 | return global.__DEV__ ? : null
310 | }
311 | }
312 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rnvconsole",
3 | "version": "0.1.0",
4 | "lockfileVersion": 1
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rnvconsole",
3 | "version": "0.3.0",
4 | "description": "vconsole for react native",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/fwon/RNVConsole.git"
12 | },
13 | "keywords": [
14 | "vconsole;",
15 | "debugger;",
16 | "console;",
17 | "react-native;"
18 | ],
19 | "peerDependencies": {
20 | "react": "*",
21 | "react-native": ">=0.50.0"
22 | },
23 | "author": "fwon",
24 | "license": "MIT",
25 | "bugs": {
26 | "url": "https://github.com/fwon/RNVConsole/issues"
27 | },
28 | "homepage": "https://github.com/fwon/RNVConsole#readme"
29 | }
30 |
--------------------------------------------------------------------------------
/src/console.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { FlatList, Text, StyleSheet, View } from 'react-native'
3 | import event from './event'
4 | import { debounce } from './tool'
5 |
6 | let logStack = null
7 |
8 | // log 消息类
9 | class LogStack {
10 | constructor() {
11 | this.logs = []
12 | this.maxLength = 100
13 | this.listeners = []
14 | this.notify = debounce(500, false, this.notify)
15 | }
16 |
17 | getLogs() {
18 | return this.logs
19 | }
20 |
21 | addLog(method, data) {
22 | if (this.logs.length > this.maxLength) {
23 | this.logs = this.logs.slice(1)
24 | }
25 | const date = new Date()
26 | this.logs.push({
27 | method,
28 | data: strLog(data),
29 | time: `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}:${date.getMilliseconds()}`,
30 | id: unixId()
31 | })
32 | this.notify()
33 | }
34 |
35 | clearLogs() {
36 | this.logs = []
37 | this.notify()
38 | }
39 |
40 | notify() {
41 | this.listeners.forEach(callback => {
42 | callback()
43 | })
44 | }
45 |
46 | attach(callback) {
47 | this.listeners.push(callback)
48 | }
49 | }
50 |
51 | class Console extends Component {
52 | constructor(props) {
53 | super(props)
54 | this.name = 'Log'
55 | this.mountState = false
56 | this.state = {
57 | logs: []
58 | }
59 | logStack.attach(() => {
60 | if (this.mountState) {
61 | const logs = logStack.getLogs()
62 | this.setState({
63 | logs
64 | })
65 | }
66 | })
67 | }
68 |
69 | componentDidMount() {
70 | this.mountState = true
71 | this.setState({
72 | logs: logStack.getLogs()
73 | })
74 | // 类方法用bind会指向不同地址,导致off失败
75 | event.on('clear', this.clearLogs)
76 | event.on('addLog', this.addLog)
77 | }
78 |
79 | componentWillUnmount() {
80 | this.mountState = false
81 | event.off('clear', this.clearLogs)
82 | event.off('addLog', this.addLog)
83 | }
84 |
85 | shouldComponentUpdate(nextProps, nextState) {
86 | return nextState.logs.length !== this.state.logs.length
87 | }
88 |
89 | addLog = msg => {
90 | logStack.addLog('log', [msg])
91 | }
92 |
93 | clearLogs = name => {
94 | if (name === this.name) {
95 | logStack.clearLogs()
96 | }
97 | }
98 |
99 | scrollToEnd = () => {
100 | this.flatList.scrollToEnd({ animated: true })
101 | }
102 |
103 | renderLogItem({ item }) {
104 | return (
105 |
106 | {item.time}
107 | {item.data}
108 |
109 | )
110 | }
111 |
112 | render() {
113 | return (
114 | {
116 | this.flatList = ref
117 | }}
118 | legacyImplementation
119 | // onLayout={() => this.flatList.scrollToEnd({ animated: true })}
120 | // initialNumToRender={20}
121 | showsVerticalScrollIndicator
122 | extraData={this.state}
123 | data={this.state.logs}
124 | renderItem={this.renderLogItem}
125 | ListEmptyComponent={() => Loading...}
126 | keyExtractor={item => item.id}
127 | />
128 | )
129 | }
130 | }
131 |
132 | const styles = StyleSheet.create({
133 | log: {
134 | color: '#000'
135 | },
136 | warn: {
137 | color: 'orange',
138 | backgroundColor: '#fffacd',
139 | borderColor: '#ffb930'
140 | },
141 | error: {
142 | color: '#dc143c',
143 | backgroundColor: '#ffe4e1',
144 | borderColor: '#f4a0ab'
145 | },
146 | logItem: {
147 | borderBottomWidth: StyleSheet.hairlineWidth,
148 | borderColor: '#eee'
149 | },
150 | logItemText: {
151 | fontSize: 12,
152 | paddingHorizontal: 10,
153 | paddingVertical: 8
154 | },
155 | logItemTime: {
156 | fontSize: 11,
157 | fontWeight: '700',
158 | textAlign: 'center'
159 | }
160 | })
161 |
162 | function unixId() {
163 | return Math.round(Math.random() * 1000000).toString(16)
164 | }
165 |
166 | function strLog(logs) {
167 | const arr = logs.map(data => formatLog(data))
168 | return arr.join(' ')
169 | }
170 |
171 | function formatLog(obj) {
172 | if (
173 | obj === null ||
174 | obj === undefined ||
175 | typeof obj === 'string' ||
176 | typeof obj === 'number' ||
177 | typeof obj === 'boolean' ||
178 | typeof obj === 'function'
179 | ) {
180 | return `"${String(obj)}"`
181 | }
182 | if (obj instanceof Date) {
183 | return `Date(${obj.toISOString()})`
184 | }
185 | if (Array.isArray(obj)) {
186 | return `Array(${obj.length})[${obj.map(elem => formatLog(elem))}]`
187 | }
188 | if (obj.toString) {
189 | try {
190 | return `object(${JSON.stringify(obj, null, 2)})`
191 | } catch (err) {
192 | return '非法输入'
193 | }
194 | }
195 | return 'unknown data'
196 | }
197 |
198 | function proxyConsole(console, stack) {
199 | const methods = ['log', 'warn', 'error', 'info']
200 | methods.forEach(method => {
201 | const fn = console[method]
202 | console[method] = function(...args) {
203 | stack.addLog(method, args)
204 | fn.apply(console, args)
205 | }
206 | })
207 | }
208 |
209 | module.exports = (function() {
210 | if (!logStack) {
211 | logStack = new LogStack()
212 | }
213 | proxyConsole(global.console, logStack)
214 | return
215 | })()
216 |
--------------------------------------------------------------------------------
/src/event.js:
--------------------------------------------------------------------------------
1 | export default class Event {
2 | constructor() {
3 | this.eventList = {}
4 | }
5 |
6 | on(eventName, callback) {
7 | if (!this.eventList[eventName]) {
8 | this.eventList[eventName] = []
9 | }
10 | this.eventList[eventName].push(callback)
11 | return this
12 | }
13 |
14 | trigger(...args) {
15 | const key = Array.prototype.shift.call(args)
16 | const fns = this.eventList[key]
17 | if (!fns || fns.length === 0) {
18 | return this
19 | }
20 | for (let i = 0, fn; (fn = fns[i++]); ) {
21 | fn.apply(this, args)
22 | }
23 | return this
24 | }
25 |
26 | off(key, fn) {
27 | const fns = this.eventList[key]
28 | if (!fns) {
29 | return this
30 | }
31 | if (!fn) {
32 | if (fns) {
33 | fns.length = 0
34 | }
35 | } else {
36 | for (let i = fns.length - 1; i >= 0; i--) {
37 | const _fn = fns[i]
38 | if (_fn === fn) {
39 | fns.splice(i, 1)
40 | }
41 | }
42 | }
43 | return this
44 | }
45 | }
46 | let event
47 | module.exports = (function() {
48 | if (!event) {
49 | event = new Event()
50 | }
51 | return event
52 | })()
53 |
--------------------------------------------------------------------------------
/src/info.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { View, Text } from 'react-native'
3 |
4 | export default class Info extends Component {
5 | constructor(props) {
6 | super(props)
7 | this.state = {
8 | info: ''
9 | }
10 | }
11 |
12 | componentDidMount() {
13 | let info = this.props.info
14 | if (typeof info === 'object') {
15 | try {
16 | info = JSON.stringify(info, null, 2)
17 | } catch (err) {
18 | console.log(err)
19 | }
20 | }
21 | this.setState({
22 | info
23 | })
24 | }
25 |
26 | render() {
27 | return (
28 |
29 | {this.state.info}
30 |
31 | )
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/network.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { View, Text, StyleSheet, FlatList, TouchableOpacity } from 'react-native'
3 | import event from './event'
4 | import { debounce } from './tool'
5 |
6 | let ajaxStack = null
7 |
8 | // ajx 请求类
9 | class AjaxStack {
10 | constructor() {
11 | this.requestIds = []
12 | this.requests = {}
13 | this.maxLength = 100
14 | this.listeners = []
15 | this.notify = debounce(500, false, this.notify)
16 | }
17 |
18 | getRequestIds() {
19 | return this.requestIds
20 | }
21 |
22 | getRequests() {
23 | return this.requests
24 | }
25 |
26 | getRequest(id) {
27 | return this.requests[id] || {}
28 | }
29 |
30 | updateRequest(id, data) {
31 | // update item
32 | const item = this.requests[id] || {}
33 |
34 | if (this.requestIds.length > this.maxLength) {
35 | const _id = this.requestIds[0]
36 | this.requestIds = this.requestIds.slice(1)
37 | this.requests[id] && delete this.requests[_id]
38 | }
39 | for (const key in data) {
40 | item[key] = data[key]
41 | }
42 | // update dom
43 | const domData = {
44 | id,
45 | url: item.url,
46 | status: item.status,
47 | method: item.method || '-',
48 | costTime: item.costTime > 0 ? `${item.costTime}ms` : '-',
49 | requestHeader: item.requestHeader || null,
50 | responseHeader: item.responseHeader || null,
51 | getData: item.getData || null,
52 | postData: item.postData || null,
53 | response: null,
54 | actived: !!item.actived
55 | }
56 | switch (item.responseType) {
57 | case '':
58 | case 'text':
59 | // try to parse JSON
60 | if (typeof item.response === 'string') {
61 | try {
62 | domData.response = JSON.parse(item.response)
63 | domData.response = JSON.stringify(domData.response, null, 1)
64 | } catch (e) {
65 | // not a JSON string
66 | domData.response = item.response
67 | }
68 | } else if (typeof item.response !== 'undefined') {
69 | domData.response = Object.prototype.toString.call(item.response)
70 | }
71 | break
72 | case 'json':
73 | if (typeof item.response !== 'undefined') {
74 | domData.response = JSON.stringify(item.response, null, 1)
75 | }
76 | break
77 | case 'blob':
78 | case 'document':
79 | case 'arraybuffer':
80 | default:
81 | if (typeof item.response !== 'undefined') {
82 | domData.response = Object.prototype.toString.call(item.response)
83 | }
84 | break
85 | }
86 | if (item.readyState === 0 || item.readyState === 1) {
87 | domData.status = 'Pending'
88 | } else if (item.readyState === 2 || item.readyState === 3) {
89 | domData.status = 'Loading'
90 | } else if (item.readyState === 4) {
91 | // do nothing
92 | } else {
93 | domData.status = 'Unknown'
94 | }
95 | if (this.requestIds.indexOf(id) === -1) {
96 | this.requestIds.push(id)
97 | }
98 | this.requests[id] = domData
99 | this.notify()
100 | }
101 |
102 | clearRequests() {
103 | this.requestIds = []
104 | this.requests = {}
105 | this.notify()
106 | }
107 |
108 | notify() {
109 | this.listeners.forEach(callback => {
110 | callback()
111 | })
112 | }
113 |
114 | attach(callback) {
115 | this.listeners.push(callback)
116 | }
117 | }
118 |
119 | class Network extends Component {
120 | constructor(props) {
121 | super(props)
122 | this.name = 'Network'
123 | this.mountState = false
124 | this.state = {
125 | showingId: null,
126 | requestIds: [],
127 | requests: {}
128 | }
129 | ajaxStack.attach(() => {
130 | if (this.mountState) {
131 | this.setState({
132 | requestIds: ajaxStack.getRequestIds(),
133 | requests: ajaxStack.getRequests()
134 | })
135 | }
136 | })
137 | }
138 |
139 | componentDidMount() {
140 | this.mountState = true
141 | this.setState({
142 | requestIds: ajaxStack.getRequestIds(),
143 | requests: ajaxStack.getRequests()
144 | })
145 | event.on('clear', this.clearRequests.bind(this))
146 | }
147 |
148 | componentWillUnmount() {
149 | this.mountState = false
150 | event.off('clear', this.clearRequests.bind(this))
151 | }
152 |
153 | shouldComponentUpdate(nextProps, nextState) {
154 | return (
155 | nextState.requestIds.length !== this.state.requestIds.length ||
156 | nextState.showingId ||
157 | this.state.showingId
158 | )
159 | }
160 |
161 | clearRequests(name) {
162 | if (name === this.name) {
163 | ajaxStack.clearRequests()
164 | }
165 | }
166 |
167 | renderHeader() {
168 | const count = Object.keys(this.state.requests).length || 0
169 | return (
170 |
171 | Name ({count})
172 | Method
173 | Status
174 | Time
175 |
176 | )
177 | }
178 |
179 | renderReqItem({ item }) {
180 | const _item = this.state.requests[item] || {}
181 | return (
182 |
183 | {
185 | this.setState(state => ({
186 | showingId: state.showingId === _item.id ? null : _item.id
187 | }))
188 | }}
189 | >
190 | = 400 && styles.error
195 | ]}
196 | >
197 |
202 | {_item.url}
203 |
204 | {_item.method}
205 |
206 | {_item.status}
207 |
208 | {_item.costTime}
209 |
210 |
211 | {this.state.showingId === _item.id && (
212 |
213 |
214 | General
215 |
216 |
217 | {_item.url}
218 |
219 |
220 |
221 | {_item.requestHeader && (
222 |
223 | Request Header
224 | {Object.keys(_item.requestHeader).map(key => (
225 |
226 | {key}:
227 |
228 | {_item.requestHeader[key]}
229 |
230 |
231 | ))}
232 |
233 | )}
234 | {_item.getData && (
235 |
236 |
237 | Query String Parameters
238 |
239 | {Object.keys(_item.getData).map(key => (
240 |
241 | {key}:
242 |
243 | {_item.getData[key]}
244 |
245 |
246 | ))}
247 |
248 | )}
249 | {_item.postData && (
250 |
251 | Form Data
252 | {Object.keys(_item.postData).map(key => (
253 |
254 | {key}:
255 | {_item.postData[key]}
256 |
257 | ))}
258 |
259 | )}
260 | {_item.responseHeader && (
261 |
262 | Response Header
263 | {Object.keys(_item.responseHeader).map(key => (
264 |
265 | {key}:
266 |
267 | {_item.responseHeader[key]}
268 |
269 |
270 | ))}
271 |
272 | )}
273 |
274 | Response
275 |
276 | {_item.response || ''}
277 |
278 |
279 |
280 | )}
281 |
282 | )
283 | }
284 |
285 | render() {
286 | return (
287 | Loading...}
295 | keyExtractor={item => item}
296 | />
297 | )
298 | }
299 | }
300 |
301 | const styles = StyleSheet.create({
302 | bold: {
303 | fontWeight: '700'
304 | },
305 | active: {
306 | backgroundColor: '#fffacd'
307 | },
308 | flex3: {
309 | flex: 3
310 | },
311 | flex1: {
312 | flex: 1
313 | },
314 | error: {
315 | backgroundColor: '#ffe4e1',
316 | borderColor: '#ffb930'
317 | },
318 | nwHeader: {
319 | flexDirection: 'row',
320 | backgroundColor: '#fff'
321 | },
322 | nwHeaderTitle: {
323 | borderColor: '#eee',
324 | borderWidth: StyleSheet.hairlineWidth,
325 | paddingVertical: 4,
326 | paddingHorizontal: 2
327 | },
328 | nwItem: {},
329 | nwItemDetail: {
330 | borderColor: '#eee',
331 | borderLeftWidth: StyleSheet.hairlineWidth,
332 | marginLeft: 15
333 | },
334 | nwItemDetailHeader: {
335 | paddingLeft: 5,
336 | paddingVertical: 4,
337 | backgroundColor: '#eee'
338 | },
339 | nwDetailItem: {
340 | paddingLeft: 5
341 | },
342 | headers: {
343 | flexDirection: 'row',
344 | paddingVertical: 2,
345 | borderBottomWidth: StyleSheet.hairlineWidth,
346 | borderColor: '#ccc'
347 | },
348 | headerLeft: {
349 | flex: 1,
350 | borderRightWidth: StyleSheet.hairlineWidth,
351 | borderColor: '#ccc'
352 | },
353 | headerRight: {
354 | flex: 2,
355 | paddingLeft: 10,
356 | color: '#aaa'
357 | }
358 | })
359 |
360 | function unixId() {
361 | return Math.round(Math.random() * 1000000).toString(16)
362 | }
363 |
364 | function proxyAjax(XHR, stack) {
365 | if (!XHR) {
366 | return
367 | }
368 | const _open = XHR.prototype.open
369 | const _send = XHR.prototype.send
370 | this._open = _open
371 | this._send = _send
372 |
373 | // mock open()
374 | XHR.prototype.open = function(...args) {
375 | const XMLReq = this
376 | const method = args[0]
377 | const url = args[1]
378 | const id = unixId()
379 | let timer = null
380 |
381 | // may be used by other functions
382 | XMLReq._requestID = id
383 | XMLReq._method = method
384 | XMLReq._url = url
385 |
386 | // mock onreadystatechange
387 | const _onreadystatechange = XMLReq.onreadystatechange || function() {}
388 | const onreadystatechange = function() {
389 | const item = stack.getRequest(id)
390 | // update status
391 | item.readyState = XMLReq.readyState
392 | item.status = 0
393 | if (XMLReq.readyState > 1) {
394 | item.status = XMLReq.status
395 | }
396 | item.responseType = XMLReq.responseType
397 |
398 | if (XMLReq.readyState === 0) {
399 | // UNSENT
400 | if (!item.startTime) {
401 | item.startTime = +new Date()
402 | }
403 | } else if (XMLReq.readyState === 1) {
404 | // OPENED
405 | if (!item.startTime) {
406 | item.startTime = +new Date()
407 | }
408 | } else if (XMLReq.readyState === 2) {
409 | // HEADERS_RECEIVED
410 | item.responseHeader = {}
411 | const arr = headers.trim().split(/[\r\n]+/)
412 |
413 | // Create a map of header names to values
414 | arr.forEach(line => {
415 | const parts = line.split(': ')
416 | const header = parts.shift()
417 | const value = parts.join(': ')
418 | item.responseHeader[header] = value
419 | })
420 | } else if (XMLReq.readyState === 3) {
421 | // LOADING
422 | } else if (XMLReq.readyState === 4) {
423 | // DONE
424 | clearInterval(timer)
425 | item.endTime = +new Date()
426 | item.costTime = item.endTime - (item.startTime || item.endTime)
427 | item.response = XMLReq.response
428 | } else {
429 | clearInterval(timer)
430 | }
431 |
432 | if (!XMLReq._noVConsole) {
433 | stack.updateRequest(id, item)
434 | }
435 | return _onreadystatechange.apply(XMLReq, args)
436 | }
437 | XMLReq.onreadystatechange = onreadystatechange
438 |
439 | // some 3rd libraries will change XHR's default function
440 | // so we use a timer to avoid lost tracking of readyState
441 | let preState = -1
442 | timer = setInterval(() => {
443 | if (preState !== XMLReq.readyState) {
444 | preState = XMLReq.readyState
445 | onreadystatechange.call(XMLReq)
446 | }
447 | }, 10)
448 |
449 | return _open.apply(XMLReq, args)
450 | }
451 |
452 | // mock send()
453 | XHR.prototype.send = function(...args) {
454 | const XMLReq = this
455 | const data = args[0]
456 |
457 | const item = stack.getRequest(XMLReq._requestID)
458 | item.method = XMLReq._method.toUpperCase()
459 | let query = XMLReq._url.split('?') // a.php?b=c&d=?e => ['a.php', 'b=c&d=', '?e']
460 | item.url = query.shift() // => ['b=c&d=', '?e']
461 |
462 | if (query.length > 0) {
463 | item.getData = {}
464 | query = query.join('?') // => 'b=c&d=?e'
465 | query = query.split('&') // => ['b=c', 'd=?e']
466 | for (let q of query) {
467 | q = q.split('=')
468 | item.getData[q[0]] = decodeURIComponent(q[1])
469 | }
470 | }
471 |
472 | if (XMLReq._headers) {
473 | item.requestHeader = XMLReq._headers
474 | }
475 |
476 | if (item.method === 'POST') {
477 | // save POST data
478 | if (typeof data === 'string') {
479 | const arr = data.split('&')
480 | item.postData = {}
481 | for (let q of arr) {
482 | q = q.split('=')
483 | item.postData[q[0]] = q[1]
484 | }
485 | } else {
486 | item.postData = data
487 | }
488 | }
489 |
490 | if (!XMLReq._noVConsole) {
491 | stack.updateRequest(XMLReq._requestID, item)
492 | }
493 |
494 | return _send.apply(XMLReq, args)
495 | }
496 | }
497 |
498 | module.exports = (function() {
499 | if (!ajaxStack) {
500 | ajaxStack = new AjaxStack()
501 | }
502 | proxyAjax(global.originalXMLHttpRequest || global.XMLHttpRequest, ajaxStack)
503 | return
504 | })()
505 |
--------------------------------------------------------------------------------
/src/tool.js:
--------------------------------------------------------------------------------
1 | function throttle(delay, noTrailing, callback, debounceMode) {
2 | let timeoutID
3 | let lastExec = 0
4 | if (typeof noTrailing !== 'boolean') {
5 | debounceMode = callback
6 | callback = noTrailing
7 | noTrailing = undefined
8 | }
9 |
10 | function wrapper(...args) {
11 | const self = this
12 | const elapsed = Number(new Date()) - lastExec
13 |
14 | function exec() {
15 | lastExec = Number(new Date())
16 | callback.apply(self, args)
17 | }
18 |
19 | function clear() {
20 | timeoutID = undefined
21 | }
22 |
23 | if (debounceMode && !timeoutID) {
24 | exec()
25 | }
26 |
27 | if (timeoutID) {
28 | clearTimeout(timeoutID)
29 | }
30 |
31 | if (debounceMode === undefined && elapsed > delay) {
32 | exec()
33 | } else if (noTrailing !== true) {
34 | timeoutID = setTimeout(
35 | debounceMode ? clear : exec,
36 | debounceMode === undefined ? delay - elapsed : delay
37 | )
38 | }
39 | }
40 |
41 | return wrapper
42 | }
43 |
44 | function debounce(delay, atBegin, callback) {
45 | return callback === undefined
46 | ? throttle(delay, atBegin, false)
47 | : throttle(delay, callback, atBegin !== false)
48 | }
49 |
50 | module.exports = {
51 | throttle,
52 | debounce
53 | }
54 |
--------------------------------------------------------------------------------