@electerm/electerm-react 3.3.8 → 3.5.6

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.
@@ -8,23 +8,30 @@ import {
8
8
  } from '../../common/constants'
9
9
  import {
10
10
  ReloadOutlined,
11
- EditOutlined
11
+ EditOutlined,
12
+ UploadOutlined,
13
+ DownloadOutlined
12
14
  } from '@ant-design/icons'
13
15
  import {
14
16
  Spin,
15
17
  Select,
16
- Switch
18
+ Switch,
19
+ Tooltip
17
20
  } from 'antd'
18
21
  import * as ls from '../../common/safe-local-storage'
19
22
  import scanCode from './code-scan'
20
23
  import resolutions from './resolutions'
21
24
  import { readClipboardAsync } from '../../common/clipboard'
22
25
  import RemoteFloatControl from '../common/remote-float-control'
26
+ import HelpIcon from '../common/help-icon'
27
+ import { FileTransferManager, createFileLogger } from './file-transfer'
28
+ import { notification } from '../common/notification'
29
+ import message from '../common/message'
30
+ import ShowItem from '../common/show-item'
23
31
  import './rdp.styl'
24
32
 
25
33
  const { Option } = Select
26
34
 
27
- // IronRDP WASM module imports — loaded dynamically
28
35
  async function loadWasmModule () {
29
36
  if (window.ironRdp) return
30
37
  console.debug('[RDP-CLIENT] Loading IronRDP WASM module...')
@@ -55,9 +62,15 @@ export default class RdpSession extends PureComponent {
55
62
  this.state = {
56
63
  loading: false,
57
64
  scaleViewport,
58
- ...resObj
65
+ ...resObj,
66
+ hasRemoteFiles: false,
67
+ downloadBtnDisabled: true,
68
+ uploadReady: false
59
69
  }
60
70
  this.session = null
71
+ this.fileTransfer = null
72
+ this.fileUploadInProgress = false
73
+ this.log = createFileLogger()
61
74
  }
62
75
 
63
76
  componentDidMount () {
@@ -69,16 +82,18 @@ export default class RdpSession extends PureComponent {
69
82
  }
70
83
 
71
84
  cleanup = () => {
72
- console.debug('[RDP-CLIENT] cleanup() called')
73
85
  if (this.session) {
74
86
  try {
75
87
  this.session.shutdown()
76
- console.debug('[RDP-CLIENT] session.shutdown() called')
77
88
  } catch (e) {
78
- console.debug('[RDP-CLIENT] session.shutdown() error:', e)
89
+ this.log(`session.shutdown() error: ${e.message}`, 'error')
79
90
  }
80
91
  this.session = null
81
92
  }
93
+ if (this.fileTransfer) {
94
+ this.fileTransfer.cleanup()
95
+ this.fileTransfer = null
96
+ }
82
97
  }
83
98
 
84
99
  runInitScript = () => { }
@@ -126,7 +141,6 @@ export default class RdpSession extends PureComponent {
126
141
  ...tab
127
142
  })
128
143
 
129
- console.debug('[RDP-CLIENT] Creating RDP session term, host=', tab.host, 'port=', tab.port)
130
144
  const r = await createTerm(opts)
131
145
  .catch(err => {
132
146
  const text = err.message
@@ -135,7 +149,6 @@ export default class RdpSession extends PureComponent {
135
149
  if (!r) {
136
150
  this.setState({ loading: false })
137
151
  this.setStatus(statusMap.error)
138
- console.error('[RDP-CLIENT] createTerm failed')
139
152
  return
140
153
  }
141
154
 
@@ -143,18 +156,15 @@ export default class RdpSession extends PureComponent {
143
156
  pid, port
144
157
  } = r
145
158
  this.pid = pid
146
- console.debug('[RDP-CLIENT] Term created, pid=', pid, 'port=', port)
147
159
 
148
- // Build the WebSocket proxy address for IronRDP WASM
149
160
  const { width, height } = this.state
150
- // IronRDP connects to the proxy address, which then proxies via RDCleanPath
151
161
  const extra = `&width=${width}&height=${height}`
152
162
  const proxyAddress = this.buildWsUrl(port, 'rdp', extra)
153
- // Load WASM module if not already loaded
163
+
154
164
  try {
155
165
  await loadWasmModule()
156
166
  } catch (e) {
157
- console.error('[RDP-CLIENT] Failed to load WASM module:', e)
167
+ this.log(`Failed to load WASM module: ${e.message}`, 'error')
158
168
  this.setState({ loading: false })
159
169
  this.setStatus(statusMap.error)
160
170
  return
@@ -162,11 +172,10 @@ export default class RdpSession extends PureComponent {
162
172
 
163
173
  this.setStatus(statusMap.success)
164
174
 
165
- // Connect using IronRDP SessionBuilder
166
175
  try {
167
176
  const canvas = this.canvasRef.current
168
177
  if (!canvas) {
169
- console.error('[RDP-CLIENT] Canvas ref not available')
178
+ this.log('Canvas ref not available', 'error')
170
179
  this.setState({ loading: false })
171
180
  return
172
181
  }
@@ -177,12 +186,6 @@ export default class RdpSession extends PureComponent {
177
186
  const username = tab.username || ''
178
187
  const password = tab.password || ''
179
188
 
180
- console.debug('[RDP-CLIENT] Building IronRDP session...')
181
- console.debug('[RDP-CLIENT] destination:', destination)
182
- console.debug('[RDP-CLIENT] username:', username)
183
- console.debug('[RDP-CLIENT] proxyAddress:', proxyAddress)
184
- console.debug('[RDP-CLIENT] desktopSize:', width, 'x', height)
185
-
186
189
  const desktopSize = new window.ironRdp.DesktopSize(width, height)
187
190
  const enableCredsspExt = new window.ironRdp.Extension('enable_credssp', true)
188
191
 
@@ -196,7 +199,40 @@ export default class RdpSession extends PureComponent {
196
199
  builder.renderCanvas(canvas)
197
200
  builder.extension(enableCredsspExt)
198
201
 
199
- // Clipboard callbacks
202
+ this.fileTransfer = new FileTransferManager(
203
+ () => this.session,
204
+ this.log,
205
+ (inProgress) => {
206
+ this.fileUploadInProgress = inProgress
207
+ },
208
+ () => {
209
+ this.setState({ uploadReady: false })
210
+ },
211
+ (filePath, fileName, fileSize) => {
212
+ notification.success({
213
+ message: 'File downloaded from remote',
214
+ description: (
215
+ <ShowItem to={filePath}>
216
+ {`${fileName} (${this.fileTransfer.formatFileSize(fileSize)})`}
217
+ </ShowItem>
218
+ ),
219
+ duration: 0
220
+ })
221
+ }
222
+ )
223
+
224
+ this.fileTransfer.setStateChangeCallback((state) => {
225
+ this.setState({
226
+ hasRemoteFiles: state.hasRemoteFiles,
227
+ downloadBtnDisabled: !state.hasRemoteFiles
228
+ })
229
+ })
230
+
231
+ const fileTransferExtensions = this.fileTransfer.createExtensions()
232
+ fileTransferExtensions.forEach((ext) => {
233
+ builder.extension(ext)
234
+ })
235
+
200
236
  builder.remoteClipboardChangedCallback((clipboardData) => {
201
237
  try {
202
238
  if (clipboardData.isEmpty()) {
@@ -204,14 +240,14 @@ export default class RdpSession extends PureComponent {
204
240
  }
205
241
  const items = clipboardData.items()
206
242
  for (const item of items) {
207
- if (item.mimeType() === 'text/plain') {
243
+ const mimeType = item.mimeType()
244
+ if (mimeType === 'text/plain') {
208
245
  const text = item.value()
209
- console.debug('[RDP-CLIENT] Received clipboard text:', text)
210
246
  window.pre.writeClipboard(text)
211
247
  }
212
248
  }
213
249
  } catch (e) {
214
- console.error('[RDP-CLIENT] Clipboard error:', e)
250
+ this.log(`Clipboard error: ${e.message}`, 'error')
215
251
  }
216
252
  })
217
253
 
@@ -219,19 +255,15 @@ export default class RdpSession extends PureComponent {
219
255
  this.syncLocalToRemote()
220
256
  })
221
257
 
222
- // Cursor style callback
223
258
  builder.setCursorStyleCallbackContext(canvas)
224
259
  builder.setCursorStyleCallback(function (style) {
225
260
  canvas.style.cursor = style || 'default'
226
261
  })
227
262
 
228
- console.debug('[RDP-CLIENT] Calling builder.connect()...')
229
263
  this.session = await builder.connect()
230
264
 
231
265
  const ds = this.session.desktopSize()
232
- console.debug('[RDP-CLIENT] Connected! Desktop:', ds.width, 'x', ds.height)
233
266
 
234
- // Update canvas size to match actual desktop size
235
267
  canvas.width = ds.width
236
268
  canvas.height = ds.height
237
269
 
@@ -241,17 +273,15 @@ export default class RdpSession extends PureComponent {
241
273
 
242
274
  canvas.focus()
243
275
 
244
- // Run the session event loop (renders frames, handles protocol)
245
- console.debug('[RDP-CLIENT] Starting session.run() event loop')
246
276
  this.session.run().then((info) => {
247
- console.debug('[RDP-CLIENT] Session ended:', info.reason())
277
+ this.log(`Session ended: ${info.reason()}`, 'info')
248
278
  this.onSessionEnd()
249
279
  }).catch((e) => {
250
- console.error('[RDP-CLIENT] Session error:', this.formatError(e))
280
+ this.log(`Session error: ${this.formatError(e)}`, 'error')
251
281
  this.onSessionEnd()
252
282
  })
253
283
  } catch (e) {
254
- console.error('[RDP-CLIENT] Connection failed:', this.formatError(e))
284
+ this.log(`Connection failed: ${this.formatError(e)}`, 'error')
255
285
  this.setState({ loading: false })
256
286
  this.setStatus(statusMap.error)
257
287
  }
@@ -278,7 +308,9 @@ export default class RdpSession extends PureComponent {
278
308
  }
279
309
 
280
310
  syncLocalToRemote = async () => {
281
- if (!this.session) return
311
+ if (!this.session || this.fileUploadInProgress) {
312
+ return
313
+ }
282
314
  try {
283
315
  const text = await readClipboardAsync()
284
316
  if (text) {
@@ -287,12 +319,11 @@ export default class RdpSession extends PureComponent {
287
319
  await this.session.onClipboardPaste(data)
288
320
  }
289
321
  } catch (e) {
290
- console.error('[RDP-CLIENT] Local clipboard sync error:', e)
322
+ this.log(`Local clipboard sync error: ${e.message}`, 'error')
291
323
  }
292
324
  }
293
325
 
294
326
  onSessionEnd = () => {
295
- console.debug('[RDP-CLIENT] onSessionEnd called')
296
327
  this.session = null
297
328
  this.setStatus(statusMap.error)
298
329
  }
@@ -301,7 +332,6 @@ export default class RdpSession extends PureComponent {
301
332
  const canvas = this.canvasRef.current
302
333
  if (!canvas || this._inputHandlersSetup) return
303
334
  this._inputHandlersSetup = true
304
- console.debug('[RDP-CLIENT] Setting up input handlers')
305
335
 
306
336
  canvas.addEventListener('keydown', (e) => {
307
337
  e.preventDefault()
@@ -315,7 +345,7 @@ export default class RdpSession extends PureComponent {
315
345
  tx.addEvent(event)
316
346
  this.session.applyInputs(tx)
317
347
  } catch (err) {
318
- console.error('[RDP-CLIENT] Key press error:', err)
348
+ this.log(`Key press error: ${err.message}`, 'error')
319
349
  }
320
350
  })
321
351
 
@@ -331,7 +361,7 @@ export default class RdpSession extends PureComponent {
331
361
  tx.addEvent(event)
332
362
  this.session.applyInputs(tx)
333
363
  } catch (err) {
334
- console.error('[RDP-CLIENT] Key release error:', err)
364
+ this.log(`Key release error: ${err.message}`, 'error')
335
365
  }
336
366
  })
337
367
 
@@ -381,7 +411,7 @@ export default class RdpSession extends PureComponent {
381
411
  tx.addEvent(event)
382
412
  this.session.applyInputs(tx)
383
413
  } catch (err) {
384
- console.error('[RDP-CLIENT] Mouse down error:', err)
414
+ this.log(`Mouse down error: ${err.message}`, 'error')
385
415
  }
386
416
  })
387
417
 
@@ -394,7 +424,7 @@ export default class RdpSession extends PureComponent {
394
424
  tx.addEvent(event)
395
425
  this.session.applyInputs(tx)
396
426
  } catch (err) {
397
- console.error('[RDP-CLIENT] Mouse up error:', err)
427
+ this.log(`Mouse up error: ${err.message}`, 'error')
398
428
  }
399
429
  })
400
430
 
@@ -417,14 +447,15 @@ export default class RdpSession extends PureComponent {
417
447
  this.session.applyInputs(tx)
418
448
  }
419
449
  } catch (err) {
420
- console.error('[RDP-CLIENT] Wheel error:', err)
450
+ this.log(`Wheel error: ${err.message}`, 'error')
421
451
  }
422
452
  }, { passive: false })
423
453
 
424
454
  canvas.addEventListener('contextmenu', (e) => e.preventDefault())
425
455
 
426
- canvas.addEventListener('paste', () => {
427
- this.syncLocalToRemote()
456
+ canvas.addEventListener('paste', (e) => {
457
+ e.preventDefault()
458
+ this.handlePasteEvent()
428
459
  })
429
460
 
430
461
  canvas.addEventListener('focus', () => {
@@ -432,7 +463,6 @@ export default class RdpSession extends PureComponent {
432
463
  })
433
464
  }
434
465
 
435
- // Get PS/2 scancode from keyboard event code using existing code-scan module
436
466
  getScancode = (code) => {
437
467
  const sc = scanCode({ code })
438
468
  return sc !== undefined ? sc : null
@@ -443,7 +473,6 @@ export default class RdpSession extends PureComponent {
443
473
  }
444
474
 
445
475
  handleReInit = () => {
446
- console.debug('[RDP-CLIENT] handleReInit called')
447
476
  this.cleanup()
448
477
  this.props.reloadTab(
449
478
  this.props.tab
@@ -492,9 +521,9 @@ export default class RdpSession extends PureComponent {
492
521
  return {
493
522
  isFullScreen: this.props.fullscreen,
494
523
  onSendCtrlAltDel: this.handleSendCtrlAltDel,
495
- screens: [], // RDP doesn't have multi-screen support like VNC
524
+ screens: [],
496
525
  currentScreen: null,
497
- onSelectScreen: () => { }, // No-op for RDP
526
+ onSelectScreen: () => { },
498
527
  fixedPosition,
499
528
  showExitFullscreen,
500
529
  className
@@ -504,37 +533,87 @@ export default class RdpSession extends PureComponent {
504
533
  handleSendCtrlAltDel = () => {
505
534
  if (!this.session) return
506
535
  try {
507
- // Send Ctrl+Alt+Del sequence using IronRDP
508
536
  const tx = new window.ironRdp.InputTransaction()
509
-
510
- // Ctrl key press
511
- const ctrlScancode = 0x1D // Left Ctrl scancode
537
+ const ctrlScancode = 0x1D
512
538
  tx.addEvent(window.ironRdp.DeviceEvent.keyPressed(ctrlScancode))
513
-
514
- // Alt key press
515
- const altScancode = 0x38 // Left Alt scancode
539
+ const altScancode = 0x38
516
540
  tx.addEvent(window.ironRdp.DeviceEvent.keyPressed(altScancode))
517
-
518
- // Del key press
519
- const delScancode = 0x53 // Delete scancode
541
+ const delScancode = 0x53
520
542
  tx.addEvent(window.ironRdp.DeviceEvent.keyPressed(delScancode))
521
-
522
- // Del key release
523
543
  tx.addEvent(window.ironRdp.DeviceEvent.keyReleased(delScancode))
524
-
525
- // Alt key release
526
544
  tx.addEvent(window.ironRdp.DeviceEvent.keyReleased(altScancode))
527
-
528
- // Ctrl key release
529
545
  tx.addEvent(window.ironRdp.DeviceEvent.keyReleased(ctrlScancode))
530
-
531
546
  this.session.applyInputs(tx)
532
- console.log('[RDP-CLIENT] Sent Ctrl+Alt+Del')
533
547
  } catch (err) {
534
- console.error('[RDP-CLIENT] Failed to send Ctrl+Alt+Del:', err)
548
+ this.log(`Failed to send Ctrl+Alt+Del: ${err.message}`, 'error')
535
549
  }
536
550
  }
537
551
 
552
+ handleUploadButtonClick = async () => {
553
+ const properties = [
554
+ 'openFile',
555
+ 'multiSelections',
556
+ 'showHiddenFiles',
557
+ 'noResolveAliases',
558
+ 'treatPackageAsDirectory',
559
+ 'dontAddToRecent'
560
+ ]
561
+
562
+ const files = await window.api.openDialog({
563
+ title: 'Choose files to upload to remote desktop',
564
+ message: 'Choose files to upload',
565
+ properties
566
+ }).catch((err) => {
567
+ this.log(`File dialog error: ${err.message}`, 'error')
568
+ return false
569
+ })
570
+
571
+ if (!files || !files.length) {
572
+ return
573
+ }
574
+
575
+ message.info('Ready to paste on remote to upload files', 5)
576
+ this.setState({ uploadReady: true })
577
+
578
+ if (this.fileTransfer) {
579
+ await this.fileTransfer.uploadFromPaths(files)
580
+ }
581
+ }
582
+
583
+ handleDownloadButtonClick = () => {
584
+ if (this.fileTransfer) {
585
+ this.fileTransfer.downloadFiles()
586
+ }
587
+ }
588
+
589
+ handlePasteEvent = async () => {
590
+ const text = await readClipboardAsync()
591
+
592
+ if (!text) {
593
+ this.syncLocalToRemote()
594
+ return
595
+ }
596
+
597
+ const fileRegWin = /^\w:\\.+/
598
+ const fileReg = /^\/.+/
599
+ const lines = text.split('\n')
600
+
601
+ const filePaths = lines.filter(line => fileReg.test(line) || fileRegWin.test(line))
602
+
603
+ if (filePaths.length === 0) {
604
+ this.syncLocalToRemote()
605
+ return
606
+ }
607
+
608
+ if (this.fileTransfer) {
609
+ await this.fileTransfer.uploadFromPaths(filePaths)
610
+ }
611
+ }
612
+
613
+ componentDidUpdate () {
614
+ this.setupInputHandlers()
615
+ }
616
+
538
617
  renderControl = () => {
539
618
  const contrlProps = this.getControlProps({
540
619
  fixedPosition: false,
@@ -542,7 +621,9 @@ export default class RdpSession extends PureComponent {
542
621
  className: 'mg1l'
543
622
  })
544
623
  const {
545
- id
624
+ id,
625
+ hasRemoteFiles,
626
+ uploadReady
546
627
  } = this.state
547
628
  const sleProps = {
548
629
  value: id,
@@ -556,6 +637,8 @@ export default class RdpSession extends PureComponent {
556
637
  checkedChildren: window.translate('scaleViewport'),
557
638
  className: 'mg1l'
558
639
  }
640
+ const uploadTitle = window.translate('upload') || 'Upload files to remote'
641
+ const downloadTitle = window.translate('download') || 'Download files from remote'
559
642
  return (
560
643
  <div
561
644
  className='pd1 fix session-v-info block'
@@ -587,10 +670,25 @@ export default class RdpSession extends PureComponent {
587
670
  className='mg2r mg1l pointer'
588
671
  />
589
672
  {this.renderInfo()}
590
- {this.renderHelp()}
591
673
  <Switch
592
674
  {...scaleProps}
593
675
  />
676
+ <Tooltip title={uploadTitle}>
677
+ <UploadOutlined
678
+ onClick={this.handleUploadButtonClick}
679
+ className={`mg1r mg2l pointer rdp-file-transfer-btn${uploadReady ? ' rdp-download-flash' : ''}`}
680
+ />
681
+ </Tooltip>
682
+ <Tooltip title={downloadTitle}>
683
+ <DownloadOutlined
684
+ onClick={this.handleDownloadButtonClick}
685
+ className={`mg2r mg1l pointer rdp-file-transfer-btn${hasRemoteFiles ? ' rdp-download-flash' : ' rdp-download-disabled'}`}
686
+ />
687
+ </Tooltip>
688
+ <HelpIcon
689
+ link='https://github.com/electerm/electerm/wiki/RDP-File-Transfer'
690
+ className='mg2r mg1l'
691
+ />
594
692
  </div>
595
693
  <div className='fright'>
596
694
  {this.props.fullscreenIcon()}
@@ -613,11 +711,6 @@ export default class RdpSession extends PureComponent {
613
711
  )
614
712
  }
615
713
 
616
- componentDidUpdate () {
617
- // Set up native input handlers after canvas is rendered
618
- this.setupInputHandlers()
619
- }
620
-
621
714
  render () {
622
715
  const { width: w, height: h } = this.props
623
716
  const { width, height, loading, scaleViewport } = this.state
@@ -32,4 +32,31 @@
32
32
  background var(--main)
33
33
  z-index 299
34
34
 
35
+ .rdp-file-transfer-btn
36
+ font-size: 16px
37
+ color: var(--text)
38
+ transition: color 0.2s
39
+ &:hover
40
+ color: var(--primary)
41
+
42
+ .rdp-download-disabled
43
+ opacity: 0.4
44
+ cursor: not-allowed
45
+ &:hover
46
+ color: var(--text)
47
+
48
+ canvas
49
+ outline: none
50
+
51
+ @keyframes rdp-download-flash
52
+ 0%, 100%
53
+ color: var(--primary)
54
+ transform: scale(1)
55
+ 50%
56
+ color: var(--success)
57
+ transform: scale(1.2)
58
+
59
+ .rdp-download-flash
60
+ animation: rdp-download-flash 1s ease-in-out infinite
61
+
35
62
 
@@ -1,3 +1,4 @@
1
+ import React, { useRef, useEffect } from 'react'
1
2
  import {
2
3
  ArrowUpOutlined,
3
4
  EyeInvisibleFilled,
@@ -134,15 +135,28 @@ export default function AddressBar (props) {
134
135
  const GoIcon = isLoadingRemote
135
136
  ? LoadingOutlined
136
137
  : (realPath === path ? ReloadOutlined : ArrowRightOutlined)
138
+ const inputRef = useRef(null)
139
+
140
+ useEffect(() => {
141
+ const wrapEl = inputRef.current
142
+ if (!wrapEl) return
143
+ const inputEl = wrapEl.querySelector('input')
144
+ if (!inputEl) return
145
+ const handler = () => props.onInputFocus(type)
146
+ inputEl.addEventListener('click', handler)
147
+ return () => {
148
+ inputEl.removeEventListener('click', handler)
149
+ }
150
+ }, [type])
151
+
137
152
  return (
138
153
  <div className='pd1y sftp-title-wrap'>
139
- <div className='sftp-title'>
154
+ <div className='sftp-title' ref={inputRef}>
140
155
  <Input
141
156
  value={path}
142
157
  onChange={e => props.onChange(e, n)}
143
158
  onPressEnter={e => props.onGoto(type, e)}
144
159
  prefix={renderAddonBefore(props, realPath)}
145
- onFocus={() => props.onInputFocus(type)}
146
160
  onBlur={() => props.onInputBlur(type)}
147
161
  disabled={loadingSftp}
148
162
  suffix={
@@ -42,8 +42,10 @@ export default auto(function InfoModal (props) {
42
42
  upgradeInfo
43
43
  } = props
44
44
  const onCheckUpdating = upgradeInfo.checkingRemoteVersion || upgradeInfo.upgrading
45
+ const { noUpdateMessage, noUpdateMessageExpires } = upgradeInfo
46
+ const showMessage = noUpdateMessage && noUpdateMessageExpires && Date.now() < noUpdateMessageExpires
45
47
  return (
46
- <p className='mg1b mg2t'>
48
+ <div className='mg1b mg2t'>
47
49
  <Button
48
50
  type='primary'
49
51
  loading={onCheckUpdating}
@@ -51,7 +53,10 @@ export default auto(function InfoModal (props) {
51
53
  >
52
54
  {e('checkForUpdate')}
53
55
  </Button>
54
- </p>
56
+ {showMessage && (
57
+ <span className='mg1l update-msg'>{noUpdateMessage}</span>
58
+ )}
59
+ </div>
55
60
  )
56
61
  }
57
62
 
@@ -5,8 +5,8 @@
5
5
  import { refsStatic } from '../components/common/ref'
6
6
 
7
7
  export default Store => {
8
- Store.prototype.onCheckUpdate = (noSkip = true) => {
9
- refsStatic.get('upgrade')?.appUpdateCheck(noSkip)
8
+ Store.prototype.onCheckUpdate = (isManual = false) => {
9
+ refsStatic.get('upgrade')?.appUpdateCheck(isManual)
10
10
  }
11
11
  Store.prototype.getProxySetting = function () {
12
12
  const {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "3.3.8",
3
+ "version": "3.5.6",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",