| | |
| | | const COMMON_OSS_HOST = '"https://spre.xiaopeng.com"'; // 官渠通用 oss 资源前缀 |
| | | const ADMIN_OSS_HOST = '"https://pre-xps01.xiaopeng.com"'; // 运营后台上传的额文件的 oss 域名 |
| | | |
| | | // const ROBOTAXI_HOST = '"http://192.168.110.80:8000"'; // 打车接口域名 |
| | | // const ROBOTAXI_WS_HOST = '"ws://192.168.110.34:8888/websocket"'; |
| | | const ROBOTAXI_HOST = '"http://221.182.45.100:8000"'; |
| | | const ROBOTAXI_WS_HOST = '"ws://221.182.45.100:8888/websocket"'; |
| | | const ROBOTAXI_HOST = '"http://192.168.110.80:8000"'; // 打车接口域名 |
| | | const ROBOTAXI_WS_HOST = '"ws://192.168.110.34:8888/websocket"'; |
| | | // const ROBOTAXI_HOST = '"http://221.182.45.100:8000"'; |
| | | // const ROBOTAXI_WS_HOST = '"ws://221.182.45.100:8888/websocket"'; |
| | | |
| | | const XP_APPID_MAP = { |
| | | weapp: '"xxxxxxx"', // 用户中心登录appId |
| | |
| | | |
| | | |
| | | export const PUBLIC_HEADER = { |
| | | 'XP-Uid': '-1', // 登录后属性 |
| | | 'XP-Uid': '1', // 登录后属性 |
| | | 'Content-Type': 'application/json', // 数据格式,固定 |
| | | 'XP-Authorization': '', // 计算属性 |
| | | 'XP-Encryption-Type': '3', // 加密方式,对应base64 |
| | |
| | | extractPointsFromPolygons, |
| | | convertAdcodeToCityCode, |
| | | handleBannerJump, |
| | | fetchMapData |
| | | fetchMapData, |
| | | getLocationByIP |
| | | } from '../../../utils/Robotaxi-public-method'; |
| | | import { _ROBOTAXI_WS_HOST } from '../../../constants/api'; |
| | | import { getGeofence, getBannerList } from '../../../robotaxi/api/home'; |
| | | import { getGeofence, getBannerList, getAgreement } from '../../../robotaxi/api/home'; |
| | | import { getOrderDistance } from '../../../robotaxi/api/select-map-site'; |
| | | import { checkOtp } from '../../../utils/tools'; |
| | | |
| | |
| | | const auth = res.authSetting['scope.userLocation']; |
| | | if (auth === false) { |
| | | setIsLocationAuthDenied(true); |
| | | setLatitude(DEFAULT_LATITUDE); |
| | | setLongitude(DEFAULT_LONGITUDE); |
| | | // 检查是否有选中的城市缓存 |
| | | const selectedCity = Taro.getStorageSync('selectedCity'); |
| | | if (!selectedCity || !selectedCity.code) { |
| | | setSelectedCity({ |
| | | name: '北京市', |
| | | code: '110000', |
| | | }) |
| | | Taro.setStorageSync('selectedCity', { |
| | | name: '北京市', |
| | | code: '110000', |
| | | // 没有缓存,尝试通过 IP 获取位置 |
| | | getLocationByIP().then(res => { |
| | | setLatitude(res.latitude); |
| | | setLongitude(res.longitude); |
| | | const cityName = res.city || res.province || '北京市'; |
| | | const cityCode = convertAdcodeToCityCode(res.adcode, cityName, res.province); |
| | | const cityInfo = { |
| | | name: cityName, |
| | | code: cityCode |
| | | }; |
| | | setSelectedCity(cityInfo); |
| | | Taro.setStorageSync('selectedCity', cityInfo); |
| | | }).catch(() => { |
| | | // IP 获取失败,使用默认值 |
| | | setLatitude(DEFAULT_LATITUDE); |
| | | setLongitude(DEFAULT_LONGITUDE); |
| | | setSelectedCity({ |
| | | name: '北京市', |
| | | code: '110000', |
| | | }) |
| | | Taro.setStorageSync('selectedCity', { |
| | | name: '北京市', |
| | | code: '110000', |
| | | }); |
| | | }); |
| | | } else { |
| | | // 有缓存,但授权被拒绝,仍然可以尝试通过 IP 更新中心点 |
| | | getLocationByIP().then(res => { |
| | | setLatitude(res.latitude); |
| | | setLongitude(res.longitude); |
| | | }).catch(() => { |
| | | setLatitude(DEFAULT_LATITUDE); |
| | | setLongitude(DEFAULT_LONGITUDE); |
| | | }); |
| | | } |
| | | } else { |
| | |
| | | |
| | | // 监听隐私政策显示状态 |
| | | useEffect(() => { |
| | | setShowPrivacy(Taro.getStorageSync('has_show_privicy_Robotaxi') != 'true'); |
| | | checkOtp(false).then(otp => { |
| | | if (otp) { |
| | | getAgreement().then((res: any) => { |
| | | console.log('获取协议状态:', res); |
| | | // 如果接口返回未同意,则显示弹窗 |
| | | if (res === false) { |
| | | setShowPrivacy(true); |
| | | Taro.setStorageSync('has_show_privicy_Robotaxi', 'false'); |
| | | } else { |
| | | setShowPrivacy(false); |
| | | Taro.setStorageSync('has_show_privicy_Robotaxi', 'true'); |
| | | } |
| | | }).catch(err => { |
| | | console.error('获取协议状态失败', err); |
| | | // 兜底逻辑:如果接口报错,根据本地缓存判断 |
| | | setShowPrivacy(Taro.getStorageSync('has_show_privicy_Robotaxi') != 'true'); |
| | | }); |
| | | } else { |
| | | // 未登录时,根据本地缓存判断 |
| | | setShowPrivacy(Taro.getStorageSync('has_show_privicy_Robotaxi') != 'true'); |
| | | } |
| | | }); |
| | | |
| | | getBannerList().then((res: any) => { |
| | | if (res && Array.isArray(res)) { |
| | |
| | | }) |
| | | .catch(() => { |
| | | setIsLocationAuthDenied(true); |
| | | setLatitude(DEFAULT_LATITUDE); |
| | | setLongitude(DEFAULT_LONGITUDE); |
| | | // 定位授权失败,尝试通过 IP 获取位置 |
| | | getLocationByIP().then(res => { |
| | | setLatitude(res.latitude); |
| | | setLongitude(res.longitude); |
| | | const cityName = res.city || res.province || '北京市'; |
| | | const cityCode = convertAdcodeToCityCode(res.adcode, cityName, res.province); |
| | | const cityInfo = { |
| | | name: cityName, |
| | | code: cityCode |
| | | }; |
| | | setSelectedCity(cityInfo); |
| | | Taro.setStorageSync('selectedCity', cityInfo); |
| | | setCurrentAddress(cityName); |
| | | setCurrentSite({ |
| | | name: cityName, |
| | | latitude: res.latitude, |
| | | longitude: res.longitude |
| | | }); |
| | | }).catch(() => { |
| | | setLatitude(DEFAULT_LATITUDE); |
| | | setLongitude(DEFAULT_LONGITUDE); |
| | | }); |
| | | }); |
| | | } |
| | | |
| | |
| | | method: 'POST', |
| | | payload |
| | | }); |
| | | } |
| | | |
| | | // 取消订单 |
| | | export const cancelOrder = async (orderId: string | number) => { |
| | | return request({ |
| | | url: _ROBOTAXI_HOST + `/order/api/v1/mini-program/order/cancel/${orderId}`, |
| | | method: 'PUT' |
| | | }); |
| | | } |
| | |
| | | method: 'POST', |
| | | payload |
| | | }); |
| | | } |
| | | |
| | | // 获取协议状态 |
| | | export const getAgreement = async () => { |
| | | return request({ |
| | | url: _ROBOTAXI_HOST + '/operation/api/v1/mini-program/privacy/getAgreement', |
| | | method: 'POST' |
| | | }); |
| | | } |
| New file |
| | |
| | | import request from '../../utils/request'; |
| | | import { _ROBOTAXI_HOST } from '../../constants/api'; |
| | | |
| | | /** |
| | | * 获取定价规则 |
| | | * @param openCityId 城市ID |
| | | */ |
| | | export const getPricingRules = async (openCityId: string | number) => { |
| | | return request({ |
| | | url: `${_ROBOTAXI_HOST}/operation/base/v1/mini-program/pricing/rules/${openCityId}`, |
| | | method: 'GET' |
| | | }); |
| | | }; |
| | |
| | | 车身编码: {vehicleInfo?.vehicleCode} |
| | | </View> |
| | | </View> |
| | | <Image className="car-card-detail-img" src={vehicleInfo?.vehiclePictures} /> |
| | | <Image className="car-card-detail-img" src={vehicleInfo?.vehiclePictures?.[0].url || ''} /> |
| | | </View> |
| | | { |
| | | tripStatus == 4 && <View> |
| | |
| | | queryRouteAndFare, |
| | | queryVehicleByOrderId, |
| | | getOrderPayment, |
| | | cancelOrder, |
| | | } from '../../../robotaxi/api/begin-route'; |
| | | |
| | | export default function SelectMapSite() { |
| | |
| | | } |
| | | |
| | | // 确认取消 |
| | | const handleConfirmCancel = () => { |
| | | const handleConfirmCancel = async () => { |
| | | const targetOrderId = orderId || Taro.getStorageSync('orderId'); |
| | | if (targetOrderId) { |
| | | try { |
| | | await cancelOrder(targetOrderId); |
| | | Taro.showToast({ |
| | | title: '行程已取消', |
| | | icon: 'success', |
| | | }); |
| | | } catch (error) { |
| | | Taro.showToast({ |
| | | title: '取消失败,请重试', |
| | | icon: 'none', |
| | | }); |
| | | return; |
| | | } |
| | | } |
| | | setShowCancelPopup(false); |
| | | setTripStatus(1); |
| | | setOrderId(''); |
| | | Taro.removeStorageSync('orderRouteDuration'); |
| | | Taro.removeStorageSync('orderRouteDistance'); |
| | | Taro.removeStorageSync('orderRoutePoints'); |
| | | Taro.removeStorageSync('orderRouteLatestType'); |
| | | Taro.removeStorageSync('orderId'); |
| | | Taro.removeStorageSync('tripStatus'); |
| | | } |
| | | |
| | | // 弹窗操作 |
| | |
| | | if (start) setStartSite(start); |
| | | if (end) setEndSite(end); |
| | | }, []); |
| | | const toPricingRule = () => { |
| | | const cityID = Taro.getStorageSync('selectedCity').id; |
| | | Taro.navigateTo({ |
| | | url: '/robotaxi/pages/pricing-rules/index?cityID=' + cityID, |
| | | }) |
| | | } |
| | | |
| | | if (!pricingCard) return null; |
| | | |
| | |
| | | 当前预估价是基于实时交通情况、预估里程和时长等因素得出;若中途发生修改目的地、路线以及加途径点、里程时长偏差较大等场景,订单结算金额将会重新计算。高速路桥费、停车费、超时等待费、远程调度费、跨域费等,将按行驶过程中实际产生的费用额外收取。 |
| | | </Text> |
| | | {/* 查看计价规则 */} |
| | | <View className='look-rule'> |
| | | <View className='look-rule' onClick={()=> toPricingRule()}> |
| | | <Text className='look-rule-txt'>查看计价规则</Text> |
| | | <Image className='arrow-icon' src={Subtract}></Image> |
| | | </View> |
| | |
| | | |
| | | const getList = async (page: number, isRefresh = false) => { |
| | | try { |
| | | const res = await getOrderHistoryList({ pageNum: page, pageSize, status: 9 }); |
| | | const res = await getOrderHistoryList({ pageNum: page, pageSize: pageSize }); |
| | | const newList = (res && (res as any).list) || []; |
| | | if (isRefresh) { |
| | | setGroups(newList); |
| | |
| | | flex-direction: column; |
| | | padding: 72px 64px; |
| | | |
| | | .rich-text-wrapper { |
| | | width: 100%; |
| | | font-size: 28px; |
| | | line-height: 44px; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | word-break: break-word; |
| | | overflow-wrap: break-word; |
| | | } |
| | | |
| | | .title { |
| | | font-family: PingFang SC; |
| | | font-size: 28px; |
| | |
| | | color: rgba(0, 0, 0, 0.6); |
| | | } |
| | | |
| | | .loading-box, .no-data { |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 28px; |
| | | color: rgba(0, 0, 0, 0.6); |
| | | } |
| | | |
| | | } |
| | | |
| | | .relative { |
| | |
| | | |
| | | .color-red { |
| | | color: #FF4D4F !important; |
| | | } |
| | | } |
| | |
| | | import Taro from '@tarojs/taro'; |
| | | import { Component, useState, useEffect } from 'react'; |
| | | import { View, Text, Image, Input } from '@tarojs/components'; |
| | | import Taro, { useRouter } from '@tarojs/taro'; |
| | | import { useState, useEffect } from 'react'; |
| | | import { View, Text, RichText } from '@tarojs/components'; |
| | | import { getPricingRules } from '../../api/pricing'; |
| | | import './index.less'; |
| | | import Subtract from '../../assets/Subtract.png'; |
| | | |
| | | export default function PricingRulesPage() { |
| | | const router = useRouter(); |
| | | const { cityID } = router.params; |
| | | const [pricingData, setPricingData] = useState<any>(null); |
| | | const [loading, setLoading] = useState(true); |
| | | const [pricingHtml, setPricingHtml] = useState<string>(''); |
| | | const [pricingNodes, setPricingNodes] = useState<any[]>([]); |
| | | |
| | | useEffect(() => { |
| | | if (cityID) { |
| | | fetchPricingRules(cityID); |
| | | } else { |
| | | setLoading(false); |
| | | } |
| | | }, [cityID]); |
| | | |
| | | const extractHtmlFromData = (data: any): string => { |
| | | if (!data) return ''; |
| | | if (typeof data === 'string') return data; |
| | | const candidates = ['html', 'content', 'richText', 'rich_text', 'data']; |
| | | for (const key of candidates) { |
| | | const val = (data as any)?.[key]; |
| | | if (typeof val === 'string') return val; |
| | | } |
| | | return ''; |
| | | }; |
| | | |
| | | const parseHtmlToNodes = async (html: string) => { |
| | | if (!html) { |
| | | setPricingNodes([]); |
| | | return; |
| | | } |
| | | try { |
| | | const { parse } = await import('mini-html-parser2'); |
| | | parse(html, (_err: any, nodes: any[]) => { |
| | | const enhanceNodes = (ns: any[]): any[] => |
| | | ns.map((n: any) => { |
| | | const node = { ...n }; |
| | | if (node.name === 'img') { |
| | | node.attrs = { |
| | | ...(node.attrs || {}), |
| | | style: `${node.attrs?.style || ''};max-width:100%;height:auto;display:block;` |
| | | }; |
| | | } |
| | | if (Array.isArray(node.children) && node.children.length > 0) { |
| | | node.children = enhanceNodes(node.children); |
| | | } |
| | | return node; |
| | | }); |
| | | setPricingNodes(enhanceNodes(nodes || [])); |
| | | }); |
| | | } catch { |
| | | setPricingNodes([]); |
| | | } |
| | | }; |
| | | |
| | | const fetchPricingRules = async (cityId: string) => { |
| | | try { |
| | | setLoading(true); |
| | | const res: any = await getPricingRules(cityId); |
| | | const data = res?.data ?? res; |
| | | setPricingData(data); |
| | | const html = extractHtmlFromData(data); |
| | | setPricingHtml(html); |
| | | await parseHtmlToNodes(html); |
| | | } catch (error) { |
| | | console.error('获取定价规则失败', error); |
| | | Taro.showToast({ |
| | | title: '获取定价规则失败', |
| | | icon: 'none' |
| | | }); |
| | | } finally { |
| | | setLoading(false); |
| | | } |
| | | }; |
| | | |
| | | if (loading) { |
| | | return ( |
| | | <View className='pricing-rules-page'> |
| | | <View className='loading-box'> |
| | | <Text>加载中...</Text> |
| | | </View> |
| | | </View> |
| | | ); |
| | | } |
| | | |
| | | if ((!pricingHtml || pricingHtml.trim().length === 0) && (!pricingNodes || pricingNodes.length === 0)) { |
| | | return ( |
| | | <View className='pricing-rules-page'> |
| | | <View className='no-data'> |
| | | <Text>暂无定价规则</Text> |
| | | </View> |
| | | </View> |
| | | ); |
| | | } |
| | | |
| | | return ( |
| | | <View className='pricing-rules-page'> |
| | | <View className='title'>起步价(3公里,8分钟)</View> |
| | | <View className='dis'> |
| | | 包含里程3公里,包含时长8分钟,包含里程或包含时长(低速时长)任何一项超出时,将在起步价基础上累加计费。 |
| | | </View> |
| | | |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>普通时段</View> |
| | | <View className='item-value'>4.0元 </View> |
| | | </View> |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>00:00-07:00</View> |
| | | <View className='item-value'>4.0元</View> |
| | | </View> |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>07:00-18:00</View> |
| | | <View className='item-value'>4.0元</View> |
| | | </View> |
| | | {/* 里程费 */} |
| | | <View className='item-title'>里程费</View> |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>普通时段</View> |
| | | <View className='item-value'>1.0元</View> |
| | | </View> |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>普通时段</View> |
| | | <View className='item-value'>1.0元</View> |
| | | </View> |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>普通时段</View> |
| | | <View className='item-value'>1.0元</View> |
| | | </View> |
| | | {/* 时长费 */} |
| | | <View className='item-title'>时长费</View> |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>普通时段</View> |
| | | <View className='item-value'>1.0元</View> |
| | | </View> |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>普通时段</View> |
| | | <View className='item-value'>1.0元</View> |
| | | </View> |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>普通时段</View> |
| | | <View className='item-value'>1.0元</View> |
| | | </View> |
| | | |
| | | {/* 远途费 */} |
| | | <View className='item-title'>远途费</View> |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>普通时段</View> |
| | | <View className='item-value'>1.0元</View> |
| | | </View> |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>普通时段</View> |
| | | <View className='item-value'>1.0元</View> |
| | | </View> |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>普通时段</View> |
| | | <View className='item-value'>1.0元</View> |
| | | <View className='rich-text-wrapper'> |
| | | <RichText nodes={pricingNodes && pricingNodes.length > 0 ? pricingNodes : pricingHtml} /> |
| | | </View> |
| | | </View> |
| | | ) |
| | | ); |
| | | } |
| | |
| | | } |
| | | if (pageType === 'end') { |
| | | const startSiteCache = Taro.getStorageSync('startSite'); |
| | | if (startSiteCache?.electronicAreaId && selectedSite?.electronicAreaId && startSiteCache.electronicAreaId !== selectedSite.electronicAreaId) { |
| | | if (startSiteCache?.electronicAreaId && selectedSite?.electronicAreaId && startSiteCache.electronicAreaId != selectedSite.electronicAreaId) { |
| | | Taro.showToast({ |
| | | title: '起终点需在同一运营区域', |
| | | icon: 'none', |
| | |
| | | import amapFile from '../libs/amap-wx.130'; |
| | | import { AMAP_KEY } from '../constants/api'; |
| | | import amapFile from '../libs/amap-wx.130.js'; |
| | | import { AMAP_KEY, _ROBOTAXI_HOST } from '../constants/api'; |
| | | import Taro from '@tarojs/taro'; |
| | | import request from './request'; |
| | | import { getFenceCheck, getNearestSite } from '../robotaxi/api/home'; |
| | | |
| | | // 辅助函数:计算两点之间的距离(单位:米) |
| | |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | /** |
| | | * 通过 IP 获取当前位置信息(用于定位授权失败时的兜底) |
| | | */ |
| | | export const getLocationByIP = (): Promise<any> => { |
| | | return new Promise(async (resolve, reject) => { |
| | | try { |
| | | // 1. 获取当前 network IP |
| | | // 微信小程序没有直接获取公网 IP 的接口,通常由后端从请求头中获取,或者通过第三方接口获取 |
| | | // 这里模拟调用服务端提供的 IP 定位接口,假设接口会自动识别请求来源 IP |
| | | const res: any = await request({ |
| | | url: _ROBOTAXI_HOST + '/map/base/v1/mini-program/point/getPointByIp', |
| | | method: 'GET', |
| | | }); |
| | | |
| | | if (res && res.point && typeof res.point.latitude === 'number' && typeof res.point.longitude === 'number') { |
| | | resolve({ |
| | | longitude: res.point.longitude, |
| | | latitude: res.point.latitude, |
| | | city: res.city || '', |
| | | adcode: res.adcode || '' |
| | | }); |
| | | } else { |
| | | // 如果后端接口不可用或未返回数据,尝试使用第三方接口作为备选获取 IP |
| | | Taro.request({ |
| | | url: 'https://api.ipify.org?format=json', |
| | | success: (ipRes) => { |
| | | const ip = ipRes.data?.ip; |
| | | if (ip) { |
| | | // 拿着获取到的 IP 去请求服务端接口 |
| | | request({ |
| | | url: _ROBOTAXI_HOST + '/map/base/v1/mini-program/point/getPointByIp', |
| | | method: 'POST', |
| | | payload: { ip } |
| | | }).then((businessRes: any) => { |
| | | if (businessRes && businessRes.point && typeof businessRes.point.latitude === 'number' && typeof businessRes.point.longitude === 'number') { |
| | | resolve({ |
| | | longitude: businessRes.point.longitude, |
| | | latitude: businessRes.point.latitude, |
| | | city: businessRes.city || '', |
| | | adcode: businessRes.adcode || '' |
| | | }); |
| | | } else { |
| | | reject('Business IP location failed'); |
| | | } |
| | | }).catch(reject); |
| | | } else { |
| | | reject('Failed to get network IP'); |
| | | } |
| | | }, |
| | | fail: reject |
| | | }); |
| | | } |
| | | } catch (error) { |
| | | reject(error); |
| | | } |
| | | }); |
| | | }; |
| | |
| | | |
| | | // 获取uid |
| | | const uid = Taro.getStorageSync(_UID) || '-1'; |
| | | header['XP-Uid'] = uid; |
| | | header['XP-Uid'] = 1||uid; |
| | | |
| | | const ENV = process.env.TARO_ENV |
| | | const accountInfo: any = ENV === 'weapp' ? Taro.getAccountInfoSync() || {} : {}; |
| | |
| | | |
| | | // 获取uid |
| | | const uid = await getStorage(_UID) || '-1'; |
| | | header['XP-Uid'] = uid.toString(); |
| | | header['XP-Uid'] = 1||uid.toString(); |
| | | |
| | | // 获取设备id |
| | | header['XP-Deviceid'] = Taro.getStorageSync('wxOpenId') || 'default_device_id'; |
| | |
| | | const uid = await getStorage(_UID) || '-1'; |
| | | const task = Taro.connectSocket({ |
| | | header: { |
| | | 'XP-Uid': uid.toString(), |
| | | 'XP-Uid': 1 || uid.toString(), |
| | | }, |
| | | url: this.url, |
| | | success: () => { }, |
| | |
| | | this.reconnectAttempts = 0; |
| | | this.startHeartbeat(); |
| | | const uid = await getStorage(_UID) || '-1'; |
| | | this.send({ type: 'auth', appUserId: uid.toString(), timestamp: Date.now() }); |
| | | this.send({ type: 'auth', appUserId: '12345678', timestamp: Date.now() }); |
| | | this.notifyListeners('open', { status: 'connected' }); |
| | | }); |
| | | |
| | |
| | | this.reconnectAttempts = 0; |
| | | this.startHeartbeat(); |
| | | const uid = await getStorage(_UID) || '-1'; |
| | | this.send({ type: 'auth', appUserId: uid.toString(), timestamp: Date.now() }); |
| | | this.send({ type: 'auth', appUserId: '12345678', timestamp: Date.now() }); |
| | | this.notifyListeners('open', { status: 'connected' }); |
| | | }); |
| | | |