gemercheung 2 年 前
コミット
03770cbd54

+ 1 - 1
miniprogram/api/sign.ts

@@ -6,7 +6,7 @@ import { request, Response } from '../utils/http'
 interface SingResType {
   expire: number,
   sdkAppId: number,
-  sign: string
+  sign: string,
 }
 type RoomDetailRes = Response & {
   data: SingResType

+ 2 - 1
miniprogram/app.ts

@@ -27,7 +27,8 @@ App<IAppOption>({
       language: 'zh_CN'
     },
     token: '',
-    isLogin: false
+    isLogin: false,
+    roomId: ''
   },
   addVoicePropsListener(cb: Function) {
     voiceCbs.push(cb)

+ 4 - 0
miniprogram/components/auth/auth.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 1 - 0
miniprogram/components/auth/auth.scss

@@ -0,0 +1 @@
+/* components/auth/auth.wxss */

+ 32 - 0
miniprogram/components/auth/auth.ts

@@ -0,0 +1,32 @@
+// components/auth/auth.ts
+Component({
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+
+  },
+  lifetimes: {
+    attached() {
+      debugger
+      // const app = getApp<IAppOption>();
+      // const isLogin = wx.getStorageSync('isLogin')
+      // app.setLogin(isLogin)
+      // this.updateUserInfo();
+    }
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+
+  }
+})

+ 2 - 0
miniprogram/components/auth/auth.wxml

@@ -0,0 +1,2 @@
+<!--components/auth/auth.wxml-->
+<text>components/auth/auth.wxml</text>

+ 4 - 0
miniprogram/components/profilePatch/profilePatch.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 113 - 0
miniprogram/components/profilePatch/profilePatch.scss

@@ -0,0 +1,113 @@
+/* component/profilePatch.wxss */
+.pa-layer-container {
+}
+
+.pa-layer {
+  position: fixed;
+  height: calc(612rpx + env(safe-area-inset-bottom));
+  background: #FFFFFF;
+  border-radius: 8px 8px 0px 0px;
+  bottom: 0;
+  left: 0;
+  width: 100%;
+  z-index: 99999999999999;
+  padding: 52rpx 36rpx 32rpx;
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  padding-bottom: calc(env(safe-area-inset-bottom) + 36rpx);
+}
+
+
+.auth-bg {
+  width: 100vw;
+  height: 100vh;
+  background-color: rgba(0, 0, 0, 0.3);
+  z-index: 9999999999;
+  position: fixed;
+  left: 0;
+  top: 0;
+  overflow: hidden;
+}
+
+.pa-layer .title {
+  font-size: 30rpx;
+  color: #111111;
+  line-height: 80rpx;
+  text-align: center;
+}
+
+.pa-layer .content {
+  height: 406rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+
+/* .pa-layer .content .text {
+  font-size: 26rpx;
+  color: #111111;
+  padding: 28rpx 0 36rpx;
+} */
+
+.pa-layer .button {
+  line-height: 80rpx;
+  font-size: 36rpx;
+  color: #fff;
+  background: #07C563;
+  opacity: 1;
+  border-radius: 4px;
+  width: 100%;
+  text-align: center;
+}
+.pa-layer .button.disable{
+  background: #dddddd;
+}
+
+.pa-layer .content {
+  display: flex;
+  flex-direction: column;
+  /* justify-content: space-between; */
+}
+
+.pa-layer .col {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  width: 100%;
+  /* height: 180rpx; */
+  padding: 0 0;
+  border-bottom: 1rpx solid #dddddd;
+  height: 150rpx;
+}
+
+.pa-layer .col .label {
+  font-size: 30rpx;
+  padding-right: 50rpx;
+
+}
+
+.avatar-select {
+  width: 120rpx;
+  height: 120rpx;
+  border-radius: 50%;
+}
+
+.avatar-wrapper {
+  display: flex;
+  margin: 0;
+  background-color: transparent;
+  padding: 0 10rpx;
+}
+
+.update-text {
+  font-size: 30rpx;
+  color: rgb(0, 131, 253);
+  padding: 0 10rpx;
+}
+
+.ipname {
+  height: 60rpx;
+  width: 80%;
+}

+ 67 - 0
miniprogram/components/profilePatch/profilePatch.ts

@@ -0,0 +1,67 @@
+import { getUserInfo, updateUserInfo, updateAvatar } from '../../api/user'
+Component({
+  /**
+   * 组件的属性列表
+   */
+
+  properties: {
+    show: { // 属性名
+      type: Boolean,
+      value: false,
+    },
+
+  },
+  /**
+   * 组件的初始数据
+   */
+  observers: {
+    show: function (val) {
+      console.log('gemer', val)
+      this.setData({
+        ifShow: val
+      })
+    }
+  },
+  data: {
+    ifShow: true,
+    bottom: 0,
+    defaultAvatarUrl: 'https://4dkk.4dage.com/miniapp-source/daikan/avatar_default.png',
+    avatar: '',
+    nickname: ''
+  },
+  attached() {
+    // this.setData({
+    //   bottom: 0
+    // })
+    // debugger
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    updateNickName(ev) {
+      this.setData({
+        nickname: ev.detail.value
+      })
+    },
+    quitHandle: function () {
+      // getApp().setLoginProps(true)
+    },
+
+    async onChooseAvatar(e: WechatMiniprogram.TouchEvent) {
+      const {
+        avatarUrl
+      } = e.detail
+      const url = await updateAvatar(avatarUrl)
+      console.log('url', url)
+      const wxUserId = wx.getStorageSync('wxUserId');
+      await updateUserInfo({
+        wxUserId,
+        avatarUrl: url
+      })
+      await getUserInfo();
+    },
+
+  }
+})

+ 29 - 0
miniprogram/components/profilePatch/profilePatch.wxml

@@ -0,0 +1,29 @@
+<!--component/profilePatch.wxml-->
+
+<view wx:if="{{ifShow}}" class="pa-layer-container">
+  <view class="auth-bg" bindtap="quitHandle" disable-scroll="true">
+  </view>
+  <view class="pa-layer" style="bottom: {{bottom}}">
+    <view class="title">还差一步,更新微信信息即可参与活动</view>
+    <view class="content">
+      <!-- <cover-image src="/static/images/img_empower_logo@2x.png"></cover-image> -->
+      <view class="col">
+        <text class="label">头像</text>
+
+        <button class="avatar-wrapper" open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar">
+          <image class="avatar-select" src="{{avatar || defaultAvatarUrl}}"></image>
+        </button>
+        <button class="avatar-wrapper" open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar">
+          <text class="update-text">点击更新</text>
+        </button>
+      </view>
+
+      <view class="col">
+        <text class="label">昵称</text>
+        <input type="nickname" placeholder="请输入昵称" maxlength="15" value="{{nickname}}" class="ipname" bindinput="updateNickName"></input>
+      </view>
+
+    </view>
+    <button class="button {{(avatar.length> 0 && nickname.length> 0) ? 'enable':'disable'}}" bindtap="updateUserInfo">更新微信息</button>
+  </view>
+</view>

+ 1 - 1
miniprogram/components/trtc-room/trtc-room.js

@@ -42,7 +42,7 @@ Component({
   data: {
     pusher: null,
     debugPanel: true, // 是否打开组件调试面板
-    debug: false, // 是否打开player pusher 的调试信息
+    debug: true, // 是否打开player pusher 的调试信息
     streamList: [], // 用于渲染player列表,存储stram
     visibleStreamList: [], // 有音频或者视频的StreamList
     userList: [], // 扁平化的数据用来返回给用户

+ 18 - 17
miniprogram/components/voice-v4/voice.js

@@ -1,5 +1,5 @@
 // component/voice/voice.js
-
+let timer = null
 Component({
   /**
    * 组件的属性列表
@@ -28,6 +28,7 @@ Component({
         this.unpublishLocalAudio()
         this.trtcRoomContext.exitRoom()
         this.hasEnter = false
+        timer && clearInterval(timer)
         return
       }
       if (user.userId) {
@@ -141,22 +142,22 @@ Component({
             console.error('room joinRoom 进房失败:', res)
             this.hasEnter = false
           })
-          let timer = null
-          timer = setInterval(() => {
-            if (!this.successEnter) {
-              this.trtcRoomContext.enterRoom({
-                roomID: getApp().globalData.roomId
-              }).then(() => {
-                console.log('成功进入房间')
-              }).catch((res) => {
-                console.error('room joinRoom 进房失败:', res)
-                this.hasEnter = false
-              })
-            } else {
-              clearInterval(timer)
-              timer = null
-            }
-          }, 3000);
+
+          // timer = setInterval(() => {
+          //   if (!this.successEnter) {
+          //     this.trtcRoomContext.enterRoom({
+          //       roomID: getApp().globalData.roomId
+          //     }).then(() => {
+          //       console.log('成功进入房间')
+          //     }).catch((res) => {
+          //       console.error('room joinRoom 进房失败:', res)
+          //       this.hasEnter = false
+          //     })
+          //   } else {
+          //     clearInterval(timer)
+          //     timer = null
+          //   }
+          // }, 3000);
         }
       })
     },

+ 2 - 1
miniprogram/pages/index/index.json

@@ -4,6 +4,7 @@
     "t-back-top": "tdesign-miniprogram/back-top/back-top",
     "t-search": "tdesign-miniprogram/search/search",
     "t-sticky": "tdesign-miniprogram/sticky/sticky",
-    "card": "../../components/card/card"
+    "card": "../../components/card/card",
+    "auth": "../../components/auth/auth"
   }
 }

+ 3 - 3
miniprogram/pages/index/index.ts

@@ -4,7 +4,7 @@
 const app = getApp<IAppOption>()
 // import { request, Response } from '../../utils/http'
 import { getHomeRoomList, ListItem } from '../../api/roomList'
-
+import { getUserInfo } from '../../api/user'
 
 
 Page({
@@ -16,8 +16,8 @@ Page({
   },
   // 事件处理函数
 
-  onLoad() {
-
+  async onLoad() {
+    await getUserInfo()
   },
 
   async onShow() {

+ 5 - 2
miniprogram/pages/myScene/myScene.scss

@@ -4,6 +4,10 @@
   padding: 15rpx 0;
 
 }
+.wrapper{
+  width: 100vw;
+  min-height: 100vh;
+}
 
 .search {
   margin: 0 auto 0 auto;
@@ -22,12 +26,11 @@
   display: block;
   margin-top: 10rpx;
   box-sizing: border-box;
-  padding-bottom: 200rpx;
 }
 
 .backtoTop {
   .t-back-top--fixed {
-    bottom: 200rpx !important;
+    // bottom: 200rpx !important;
   }
 }
 

+ 6 - 3
miniprogram/pages/personal/personal.ts

@@ -1,7 +1,7 @@
 // pages/personal/personal.ts
 import ActionSheet, { ActionSheetTheme, ActionSheetShowOption } from 'tdesign-miniprogram/action-sheet/index';
 import { decrptPhone, getUserInfo, updateUserInfo, updateAvatar } from '../../api/user'
-let genderHandler
+let genderHandler: { close: () => void; } | null = null
 
 const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
 Page({
@@ -10,7 +10,7 @@ Page({
    * 页面的初始数据
    */
   data: {
-    avatar: 'https://cdn-we-retail.ym.tencent.com/retail-ui/components-exp/avatar/avatar-v2/1.png',
+    avatar: 'https://4dkk.4dage.com/miniapp-source/daikan/avatar_default.png',
   },
 
   /**
@@ -154,7 +154,7 @@ Page({
 
     const { index } = event.detail
     if (index > -1) {
-      debugger
+      // debugger
       console.log('index', index)
       const wxUserId = wx.getStorageSync('wxUserId');
       await updateUserInfo({
@@ -193,5 +193,8 @@ Page({
       await getUserInfo();
     }
 
+  },
+  handleCancel() {
+    genderHandler && genderHandler.close();
   }
 })

+ 46 - 0
miniprogram/pages/room/libs/audioManager.ts

@@ -0,0 +1,46 @@
+
+export interface AudioManger {
+  start: () => void
+  changeMute: (mute: boolean) => void
+  stop: () => void
+  ring: boolean
+}
+
+
+export const audioManger= (params: VoiceProps) => {
+  let ring = false
+
+  const start = () => {
+    if (!ring) {
+      ring = true
+      const app = getApp<IAppOption>();
+      if (params.roomId) {
+        app.globalData.roomId = params.roomId
+      }
+      app.setVoiceProps({
+        userId: params.userId,
+        sdkAppID: params.sdkAppID,
+        sig: params.sig,
+        noMute: false,
+        action: 'startCall'
+      })
+    }
+  }
+  const changeMute = (noMute: boolean) => {
+    const app = getApp<IAppOption>();
+    app.setVoiceProps({
+      noMute
+    })
+  }
+  const stop = () => {
+    console.error('stop', ring)
+    if (ring) {
+      ring = false
+      getApp().setVoiceProps({
+        noMute: true,
+        action: 'stopCall'
+      })
+    }
+  }
+  return { start, stop, ring, changeMute }
+}

+ 2 - 1
miniprogram/pages/room/room.json

@@ -1,5 +1,6 @@
 {
   "usingComponents": {
-    "voice": "../../components/voice-v4/voice"
+    "voice": "../../components/voice-v4/voice",
+    "profile-patch": "../../components/profilePatch/profilePatch"
   }
 }

+ 107 - 12
miniprogram/pages/room/room.ts

@@ -1,18 +1,25 @@
 // pages/room/room.ts
+
 import { fetchRoom } from '../../api/fetchRoom'
 import { getRTCSig } from '../../api/sign'
 import { server } from '../../config'
+import { authorizeRecord } from '../../utils/util'
+import { audioManger, AudioManger } from './libs/audioManager'
+const { io } = require('../../utils/socket.io-v4-no-msgpack')
 
 Page({
-
+  socket: {} as SocketIOClient.Socket,
+  audioManger: {} as AudioManger,
   /**
    * 页面的初始数据
    */
   data: {
+    patchProfile: false,
     role: 'customer',
     roomId: '',
     webUrl: '',
     userInfo: {} as GlobalUserInfo,
+    webviewParams: {} as SocketParams,
     isTour: 0,
     m: ''
 
@@ -24,10 +31,14 @@ Page({
    */
   async onLoad(options: { roomId: string, role: string, isTour: string }) {
     console.log('options', options)
+
+    const auth = await authorizeRecord();
+    console.log('auth', auth)
+
     if (options.roomId) {
       const res = await fetchRoom(options.roomId);
       console.log('data', res)
-      this.setData({ roomId: options.roomId, m: res.sceneData[0].num })
+      this.setData({ roomDetail: res, roomId: options.roomId, m: res.sceneData[0].num })
     }
     if (options.role) {
       this.setData({ role: options.role })
@@ -36,15 +47,15 @@ Page({
     if (options.isTour) {
       this.setData({ isTour: Number(options.isTour) })
     }
-    const userId = wx.getStorageSync('wxUserId')
-    const sign = await getRTCSig(userId);
 
     this.setWebViewUrl();
+    this.handleJoinSocket();
+    this.handleJoinRTC();
 
   },
 
   webViewParams() {
-    const params = {
+    const params: SocketParams = {
       vruserId: `user_${this.data.userInfo.wxUserId}`,
       roomId: `roomId_${this.data.roomId}`,
       role: this.data.role,
@@ -54,7 +65,9 @@ Page({
       m: this.data.m,
       fromMiniApp: 1,
     }
-    console.log('params', params)
+    this.setData({
+      webviewParams: params
+    })
     type Keys = keyof typeof params;
     return Object.keys(params).map(key => `${key}=${params[key as Keys]}`).join('&')
 
@@ -62,13 +75,20 @@ Page({
 
   setWebViewUrl() {
     const params = this.webViewParams();
-    const webURL = server.webview + '?=' + params
+    const webURL = server.webview + '?' + params
     console.log('webviewServer', webURL)
+    if (!this.data.webviewParams.name || !this.data.webviewParams.avatar) {
+      this.setData({
+        patchProfile: true
+      })
+      return
+    }
     this.setData({
       webUrl: webURL
     })
   },
   updateUserInfo(data?: any) {
+    console.log('webview-updateUserInfo')
     const app = getApp<IAppOption>();
     const updateUserInfo = app.globalData.userInfo
     const updateData = Object.assign({}, updateUserInfo, data)
@@ -76,6 +96,44 @@ Page({
       userInfo: updateData
     })
   },
+  handleJoinSocket() {
+
+    this.socket = io(server.sokcet, {
+      path: "/ws-sync",
+      transport: ["websocket"],
+      parser: false
+    });
+    this.socket.on('connect', async () => {
+      const params = this.data.webviewParams
+      this.socket.emit('join', {
+        userId: params.vruserId,
+        roomId: params.roomId,
+        role: params.role,
+        isClient: true
+      })
+    });
+
+
+    this.socket.on('action', this.handleSocketAction)
+
+  },
+  async handleJoinRTC() {
+    const userId = wx.getStorageSync('wxUserId')
+    const sign = await getRTCSig(userId);
+
+    this.audioManger = audioManger({
+      roomId: this.data.webviewParams.roomId,
+      userId: userId,
+      sdkAppID: sign.sdkAppId,
+      sig: sign.sign,
+      noMute: false,
+    })
+    if (!this.audioManger.ring) {
+      this.audioManger.start()
+    }
+    this.audioManger.changeMute(false)
+
+  },
   /**
    * 生命周期函数--监听页面初次渲染完成
    */
@@ -103,6 +161,7 @@ Page({
    * 生命周期函数--监听页面卸载
    */
   onUnload() {
+    this.audioManger && this.audioManger.stop()
 
   },
 
@@ -120,10 +179,46 @@ Page({
 
   },
 
-  /**
-   * 用户点击右上角分享
-   */
-  onShareAppMessage() {
-
+  onShareAppMessage: function (res) {
+    const roomId = this.data.roomId
+    const isTour = this.data.isTour
+    const newPicUrl = this.data.roomDetail.cover || 'http://video.cgaii.com/new4dage/images/images/home_2_a.jpg'
+    const base = {
+      imageUrl: newPicUrl,
+      path: `/pages/room/room?roomId=${roomId}&role=customer&isTour=${isTour}`
+    }
+    console.error('share', base)
+    if (res.from === 'button') {
+      // this.cancelShareMode()
+      return {
+        ...base,
+        title: '【好友邀请】一起来逛店吧!',
+      }
+    } else {
+      return {
+        ...base,
+        title: '【好友邀请】一起来逛店吧!',
+      }
+    }
+  },
+  handleSocketAction(action: SocketAction) {
+    console.warn('action', action)
+    switch (action.type) {
+      case 'users-muted':
+        this.handleActionMuted(action.userId, action.muted)
+        break;
+
+      default:
+        break;
+    }
+  },
+  handleActionMuted(userId: string | undefined, muted: boolean | undefined) {
+    if (userId && typeof muted !== "undefined") {
+      const f_userId = userId.replace('user_', '')
+      const app = getApp<IAppOption>();
+      if (app.globalData.userInfo?.wxUserId == f_userId) {
+        this.audioManger.changeMute(muted)
+      }
+    }
   }
 })

+ 1 - 0
miniprogram/pages/room/room.wxml

@@ -1,5 +1,6 @@
 <!--pages/room/room.wxml-->
 <voice />
+<profile-patch show="{{patchProfile}}"/>
 <block wx:if="{{ webUrl }}">
   <web-view src="{{webUrl}}" bindmessage="webmessage"></web-view>
 </block>

ファイルの差分が大きいため隠しています
+ 0 - 1
miniprogram/utils/socket.io-v4-no-msgpack.ts


+ 29 - 16
miniprogram/utils/util.ts

@@ -1,20 +1,33 @@
+export const authorizeRecord = async () => {
+  let isAuth = await new Promise((r) => {
+    wx.authorize({
+      scope: 'scope.record',
+      success: () => r(true),
+      fail: () => r(false)
+    })
+  })
 
-export const formatTime = (date: Date) => {
-  const year = date.getFullYear()
-  const month = date.getMonth() + 1
-  const day = date.getDate()
-  const hour = date.getHours()
-  const minute = date.getMinutes()
-  const second = date.getSeconds()
+  if (isAuth) return true
 
-  return (
-    [year, month, day].map(formatNumber).join('/') +
-    ' ' +
-    [hour, minute, second].map(formatNumber).join(':')
-  )
-}
+  let res = await new Promise<WechatMiniprogram.ShowModalSuccessCallbackResult | boolean>(r => {
+    wx.showModal({
+      title: '提示',
+      content: '您未授权录音,说话功能将无法使用',
+      showCancel: true,
+      confirmText: "授权",
+      confirmColor: "#52a2d8",
+      success: res => r(res),
+      fail: () => r(false)
+    })
+  })
+  if (!res || (typeof res === 'object' && res.cancel)) return;
+
+  isAuth = await new Promise((r) => {
+    wx.openSetting({
+      success: res => r(res.authSetting['scope.record']),
+      fail: () => r(false)
+    })
+  })
 
-const formatNumber = (n: number) => {
-  const s = n.toString()
-  return s[1] ? s : '0' + s
+  return isAuth
 }

+ 3 - 2
tsconfig.json

@@ -22,8 +22,9 @@
     ]
   },
   "include": [
-    "./**/*.ts"
-  ],
+    "./**/*.ts",
+    "./typings/**/*.d.ts"
+, "miniprogram/utils/socket.io-v4-no-msgpack.js"  ],
   "exclude": [
     "node_modules"
   ],

+ 26 - 3
typings/index.d.ts

@@ -1,9 +1,14 @@
 /// <reference path="./types/index.d.ts" />
 
 interface VoiceProps {
-  noMute: boolean,
-  pullUrls: string[],
+  noMute?: boolean,
+  pullUrls?: string[],
   pushUrl?: boolean
+  sdkAppID?: number
+  sig?: string
+  action?: string
+  userId?: string
+  roomId?: string
 }
 
 interface GlobalUserInfo extends WechatMiniprogram.UserInfo {
@@ -20,15 +25,33 @@ interface GlobalDataProps {
   voiceProps?: VoiceProps,
   token: string,
   isLogin: boolean
+  roomId: string
 }
 interface IAppOption {
   globalData: GlobalDataProps
   userInfoReadyCallback?: WechatMiniprogram.GetUserInfoSuccessCallback,
   addVoicePropsListener?: (cb: Function) => void
   removeVoicePropsListener?: (cb: Function) => void
-  setVoiceProps?: (param: VoiceProps) => void
+  setVoiceProps: (param: VoiceProps) => void
   setLogin: (status: boolean) => Promise<boolean>
   watch: (key: string, method: Function) => void
   unwatch: (key: string, method: Function) => void
   resetUserInfo: () => void
+}
+
+interface SocketParams {
+  vruserId: string
+  roomId: string
+  role: string
+  avatar: string
+  name: string
+  isTour: number
+  m: string
+  fromMiniApp: number
+}
+interface SocketAction {
+  type: string
+  muted?: boolean
+  userId?: string
+  members?: any[]
 }

+ 40 - 0
typings/io.d.ts

@@ -0,0 +1,40 @@
+declare let io: SocketIOClientStatic;
+
+declare module 'socket.io-client' {
+  export = io;
+}
+
+interface SocketIOClientStatic {
+  (host: string, details?: any): SocketIOClient.Socket;
+  (details?: any): SocketIOClient.Socket;
+  connect(host: string, details?: any): SocketIOClient.Socket;
+  connect(details?: any): SocketIOClient.Socket;
+  protocol: number;
+  Socket: { new (...args: any[]): SocketIOClient.Socket };
+  Manager: SocketIOClient.ManagerStatic;
+}
+
+declare namespace SocketIOClient {
+ export interface Socket {
+    on(event: string, fn: Function): Socket;
+    once(event: string, fn: Function): Socket;
+    off(event?: string, fn?: Function): Socket;
+    emit(event: string, ...args: any[]): Socket;
+    listeners(event: string): Function[];
+    hasListeners(event: string): boolean;
+    connected: boolean;
+  }
+
+  interface ManagerStatic {
+    (url: string, opts: any): SocketIOClient.Manager;
+    new (url: string, opts: any): SocketIOClient.Manager;
+  }
+
+  interface Manager {
+    reconnection(v: boolean): Manager;
+    reconnectionAttempts(v: boolean): Manager;
+    reconnectionDelay(v: boolean): Manager;
+    reconnectionDelayMax(v: boolean): Manager;
+    timeout(v: boolean): Manager;
+  }
+}