implemented foreground service for timer alerts
This commit is contained in:
parent
dbd08f2eb3
commit
e3d690e42d
7 changed files with 189 additions and 140 deletions
|
@ -1,51 +1,39 @@
|
|||
var ForegroundService = /** @class */ (function(_super) {
|
||||
__extends(ForegroundService, _super)
|
||||
function ForegroundService() {
|
||||
return (_super !== null && _super.apply(this, arguments)) || this
|
||||
}
|
||||
ForegroundService.prototype.onStartCommand = function(
|
||||
intent,
|
||||
flags,
|
||||
startId
|
||||
) {
|
||||
import { TimerNotif } from './shared/utils'
|
||||
const superProto = android.app.Service.prototype
|
||||
android.app.Service.extend('com.tns.ForegroundService', {
|
||||
onStartCommand: function(intent, flags, startId) {
|
||||
console.log('onStartCommand')
|
||||
_super.prototype.onStartCommand.call(this, intent, flags, startId)
|
||||
superProto.onStartCommand.call(this, intent, flags, startId)
|
||||
return android.app.Service.START_STICKY
|
||||
}
|
||||
ForegroundService.prototype.onCreate = function() {
|
||||
},
|
||||
onCreate: function() {
|
||||
console.log('onCreate')
|
||||
_super.prototype.onCreate.call(this)
|
||||
this.startForeground(1, this.getNotification())
|
||||
}
|
||||
ForegroundService.prototype.onBind = function(intent) {
|
||||
return _super.prototype.onBind.call(this, intent)
|
||||
}
|
||||
ForegroundService.prototype.onUnbind = function(intent) {
|
||||
return _super.prototype.onUnbind.call(this, intent)
|
||||
}
|
||||
ForegroundService.prototype.onDestroy = function() {
|
||||
superProto.onCreate.call(this)
|
||||
this.startForeground(777, this.getNotification())
|
||||
},
|
||||
onBind: function(intent) {
|
||||
return superProto.onBind.call(this, intent)
|
||||
},
|
||||
onUnbind: function(intent) {
|
||||
return superProto.onUnbind.call(this, intent)
|
||||
},
|
||||
onDestroy: function() {
|
||||
console.log('onDestroy')
|
||||
this.stopForeground(true)
|
||||
}
|
||||
ForegroundService.prototype.getNotification = function() {
|
||||
var channel = new android.app.NotificationChannel(
|
||||
'channel_01',
|
||||
'ForegroundService Channel',
|
||||
android.app.NotificationManager.IMPORTANCE_DEFAULT
|
||||
},
|
||||
getNotification: function() {
|
||||
return TimerNotif.getNotification(
|
||||
{
|
||||
bID: 'bringToFront',
|
||||
cID: 'cti',
|
||||
cName: 'Cooking Timer info',
|
||||
description: `0 ongoing, 0 paused`,
|
||||
nID: 777,
|
||||
priority: -2,
|
||||
sound: null,
|
||||
title: 'EnRecipes is running',
|
||||
},
|
||||
this.getApplicationContext()
|
||||
)
|
||||
var notificationManager = this.getSystemService(
|
||||
android.content.Context.NOTIFICATION_SERVICE
|
||||
)
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
var builder = new android.app.Notification.Builder(
|
||||
this.getApplicationContext(),
|
||||
'channel_01'
|
||||
)
|
||||
return builder.build()
|
||||
}
|
||||
ForegroundService = __decorate(
|
||||
[JavaProxy('com.tns.ForegroundService')],
|
||||
ForegroundService
|
||||
)
|
||||
return ForegroundService
|
||||
})(android.app.Service)
|
||||
},
|
||||
})
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
<Timer
|
||||
v-for="(timer, i) in activeTimers"
|
||||
:key="timer.id"
|
||||
ref="singleTimer"
|
||||
:timer="timer"
|
||||
:timerIndex="i"
|
||||
:formattedTime="formattedTime"
|
||||
|
@ -62,21 +61,23 @@ import {
|
|||
ApplicationSettings,
|
||||
AndroidApplication,
|
||||
Utils,
|
||||
Device,
|
||||
} from "@nativescript/core";
|
||||
import { mapState, mapActions } from "vuex";
|
||||
|
||||
import Action from "./modals/Action.vue";
|
||||
import CookingTimer from "./CookingTimer.vue";
|
||||
import CTSettings from "./settings/CTSettings.vue";
|
||||
import TimePickerHMS from "./modals/TimePickerHMS.vue";
|
||||
import TimerReminder from "./modals/TimerReminder.vue";
|
||||
import Timer from "./sub/Timer.vue";
|
||||
import Toast from "./sub/Toast.vue";
|
||||
import SnackBar from "./sub/SnackBar.vue";
|
||||
|
||||
import * as utils from "~/shared/utils";
|
||||
// import { fgs } from "~/foreground.android";
|
||||
import { EventBus } from "~/main";
|
||||
let undoTimer;
|
||||
let undoTimer,
|
||||
firingTimers = [];
|
||||
declare const com: any;
|
||||
|
||||
export default {
|
||||
components: { Timer, Toast, SnackBar },
|
||||
|
@ -177,33 +178,12 @@ export default {
|
|||
cID: "cti",
|
||||
cName: "Cooking Timer info",
|
||||
description: `${ongoingCount} ongoing, ${pausedCount} paused`,
|
||||
nID: 999,
|
||||
nID: 777,
|
||||
priority: -2,
|
||||
sound: null,
|
||||
title: localize("timer"),
|
||||
});
|
||||
if (activeCount <= 0) utils.TimerNotif.clear(999);
|
||||
},
|
||||
intentListener({ intent, android }) {
|
||||
let ct = "CookingTimer";
|
||||
let action = (intent || android).getStringExtra("action");
|
||||
console.log("calling: ", action);
|
||||
let comp = this.currentComponent;
|
||||
if (action == "open_timer" && this.currentComponent != ct) {
|
||||
let openTimer = setInterval(() => {
|
||||
if (comp == ct) clearInterval(openTimer);
|
||||
else {
|
||||
if (comp == "CTSettings") this.$navigateBack();
|
||||
else this.$navigateTo(CookingTimer);
|
||||
comp = ct;
|
||||
}
|
||||
Application.off(Application.launchEvent, this.intentListener);
|
||||
Application.android.off(
|
||||
AndroidApplication.activityNewIntentEvent,
|
||||
this.intentListener
|
||||
);
|
||||
}, 250);
|
||||
}
|
||||
if (activeCount <= 0) this.foregroundService(false);
|
||||
},
|
||||
fireTimer(timer) {
|
||||
console.log("firing");
|
||||
|
@ -230,18 +210,39 @@ export default {
|
|||
console.log(action, "firing");
|
||||
EventBus.$emit(bID, action);
|
||||
});
|
||||
firingTimers.push(timer);
|
||||
// if (firingTimers.length == 1) {
|
||||
// this.$showModal(TimerReminder, {
|
||||
// fullscreen: true,
|
||||
// props: {
|
||||
// timers: firingTimers,
|
||||
// stop: this.stopFiringTimers,
|
||||
// formattedTime: this.formattedTime,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
},
|
||||
startForegroundService() {
|
||||
stopFiringTimers() {
|
||||
firingTimers.forEach((e) => utils.TimerNotif.clear(e.id));
|
||||
firingTimers = [];
|
||||
},
|
||||
openReminder() {},
|
||||
foregroundService(bool) {
|
||||
const ctx = Utils.ad.getApplicationContext();
|
||||
const intent = new android.content.Intent();
|
||||
intent.setClassName(ctx, "com.tns.ForegroundService");
|
||||
ctx.startService(intent);
|
||||
const intent = new android.content.Intent(
|
||||
ctx,
|
||||
com.tns.ForegroundService.class
|
||||
);
|
||||
if (bool)
|
||||
parseInt(Device.sdkVersion) < 26
|
||||
? ctx.startService(intent)
|
||||
: ctx.startForegroundService(intent);
|
||||
else ctx.stopService(intent);
|
||||
},
|
||||
|
||||
// DATA HANDLERS
|
||||
addTimer() {
|
||||
// fgs.class
|
||||
this.startForegroundService();
|
||||
this.foregroundService(true);
|
||||
this.$showModal(TimePickerHMS, {
|
||||
props: {
|
||||
title: "ntmr",
|
||||
|
@ -300,6 +301,7 @@ export default {
|
|||
if (!noUndo) {
|
||||
this.showUndoBar("tmrClr")
|
||||
.then(() => {
|
||||
this.foregroundService(true);
|
||||
this.addActiveTimer({
|
||||
timer: temp,
|
||||
index,
|
||||
|
@ -385,11 +387,6 @@ export default {
|
|||
},
|
||||
},
|
||||
created() {
|
||||
Application.on(Application.launchEvent, this.intentListener);
|
||||
Application.android.on(
|
||||
AndroidApplication.activityNewIntentEvent,
|
||||
this.intentListener
|
||||
);
|
||||
this.clearTimerInterval();
|
||||
},
|
||||
};
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
columns="auto, *, auto, auto, auto"
|
||||
class="singleTimer"
|
||||
>
|
||||
<!-- :class="{ blink: done }" -->
|
||||
<Button
|
||||
class="ico"
|
||||
:class="{ blink: done }"
|
||||
:text="done ? icon.ring : timer.isPaused ? icon.start : icon.pause"
|
||||
@tap="!done && toggleProgress()"
|
||||
/>
|
||||
|
|
54
app/main.ts
54
app/main.ts
|
@ -12,22 +12,38 @@ const keepScreenOn = () => {
|
|||
console.log('keepScreenOn')
|
||||
const window = Application.android.startActivity.getWindow()
|
||||
const windowMgr = android.view.WindowManager
|
||||
window.addFlags(
|
||||
const flags =
|
||||
windowMgr.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
|
||||
windowMgr.LayoutParams.FLAG_TURN_SCREEN_ON |
|
||||
windowMgr.LayoutParams.FLAG_KEEP_SCREEN_ON
|
||||
window.addFlags(flags)
|
||||
function clearFlags(args) {
|
||||
args.cancel = true
|
||||
window.clearFlags(flags)
|
||||
Application.android.off(
|
||||
AndroidApplication.activityBackPressedEvent,
|
||||
clearFlags
|
||||
)
|
||||
}
|
||||
Application.android.on(
|
||||
AndroidApplication.activityBackPressedEvent,
|
||||
clearFlags
|
||||
)
|
||||
}
|
||||
}
|
||||
Application.on(Application.resumeEvent, keepScreenOn)
|
||||
|
||||
Application.on(Application.launchEvent, ({ android }) => {
|
||||
Application.on(Application.launchEvent, (args) => {
|
||||
console.log('launching')
|
||||
if (android) androidLaunchEventLocalizationHandler()
|
||||
if (args.android) {
|
||||
androidLaunchEventLocalizationHandler()
|
||||
intentListener(args)
|
||||
}
|
||||
})
|
||||
|
||||
import Vue from 'nativescript-vue'
|
||||
import EnRecipes from './components/EnRecipes.vue'
|
||||
import CookingTimer from './components/CookingTimer.vue'
|
||||
import store from './store'
|
||||
|
||||
export const EventBus = new Vue()
|
||||
|
@ -46,3 +62,35 @@ new Vue({
|
|||
store,
|
||||
render: (h) => h(EnRecipes),
|
||||
}).$start()
|
||||
|
||||
const intentListener = ({ intent, android }: any) => {
|
||||
let ct = 'CookingTimer'
|
||||
let action = (intent || android).getStringExtra('action')
|
||||
console.log('calling: ', action)
|
||||
if (action == 'open_timer' && store.state.currentComponent != ct) {
|
||||
let openTimer = setInterval(() => {
|
||||
let comp = store.state.currentComponent
|
||||
if (comp == ct) clearInterval(openTimer)
|
||||
else {
|
||||
if (comp == 'CTSettings') Vue.navigateBack()
|
||||
else {
|
||||
Vue.navigateTo(CookingTimer)
|
||||
store.commit('setComponent', 'CookingTimer')
|
||||
}
|
||||
}
|
||||
}, 250)
|
||||
}
|
||||
}
|
||||
Application.on(Application.launchEvent, () => {
|
||||
Application.android.on(
|
||||
AndroidApplication.activityNewIntentEvent,
|
||||
intentListener
|
||||
)
|
||||
})
|
||||
Application.on(Application.exitEvent, () => {
|
||||
store.commit('setComponent', 'EnRecipes')
|
||||
Application.android.off(
|
||||
AndroidApplication.activityNewIntentEvent,
|
||||
intentListener
|
||||
)
|
||||
})
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
<supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" tools:targetApi="28" />
|
||||
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" tools:targetApi="29" />
|
||||
<uses-permission android:name="android.permission.CAMERA" tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="remove" />
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
Color,
|
||||
path,
|
||||
knownFolders,
|
||||
TimerInfo,
|
||||
} from '@nativescript/core'
|
||||
let timerOne
|
||||
declare const global, android, androidx, com, java, Array: any
|
||||
|
@ -318,9 +319,12 @@ export class TimerNotif {
|
|||
const NotifySrv = ctx.getSystemService(
|
||||
android.content.Context.NOTIFICATION_SERVICE
|
||||
)
|
||||
// nID ? NotifySrv.cancel(nID) : NotifySrv.cancelAll()
|
||||
NotifySrv.cancel(nID)
|
||||
}
|
||||
static show({
|
||||
|
||||
static getNotification(
|
||||
{
|
||||
actions,
|
||||
bID,
|
||||
cID,
|
||||
|
@ -342,15 +346,18 @@ export class TimerNotif {
|
|||
sound: string
|
||||
title: string
|
||||
vibrate?: number
|
||||
}) {
|
||||
},
|
||||
ctx
|
||||
) {
|
||||
let sdkv: number = parseInt(Device.sdkVersion)
|
||||
let soundUri: any
|
||||
if (sound) soundUri = new android.net.Uri.parse(sound)
|
||||
const NotifyMgr = android.app.NotificationManager
|
||||
let ctx = Utils.ad.getApplicationContext()
|
||||
const NotifySrv = ctx.getSystemService(
|
||||
android.content.Context.NOTIFICATION_SERVICE
|
||||
)
|
||||
const NotificationCompat = androidx.core.app.NotificationCompat
|
||||
const AudioManager = android.media.AudioManager
|
||||
if (sdkv >= 26) {
|
||||
const importance =
|
||||
priority > 0 ? NotifyMgr.IMPORTANCE_HIGH : NotifyMgr.IMPORTANCE_MIN
|
||||
|
@ -371,6 +378,7 @@ export class TimerNotif {
|
|||
if (sound) Channel.setSound(soundUri, audioAttributes)
|
||||
else Channel.setSound(null, null)
|
||||
Channel.setShowBadge(true)
|
||||
Channel.setLockscreenVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
NotifySrv.createNotificationChannel(Channel)
|
||||
}
|
||||
|
||||
|
@ -390,7 +398,7 @@ export class TimerNotif {
|
|||
let actionInt1, actionInt2, actionPInt1, actionPInt2
|
||||
if (actions) {
|
||||
actionInt1 = new Intent(bID)
|
||||
actionInt1.putExtra('action', 'delay')
|
||||
actionInt1.putExtra('action', 'stop')
|
||||
actionPInt1 = PendingIntent.getBroadcast(
|
||||
ctx,
|
||||
2,
|
||||
|
@ -398,7 +406,7 @@ export class TimerNotif {
|
|||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
actionInt2 = new Intent(bID)
|
||||
actionInt2.putExtra('action', 'stop')
|
||||
actionInt2.putExtra('action', 'delay')
|
||||
actionPInt2 = PendingIntent.getBroadcast(
|
||||
ctx,
|
||||
3,
|
||||
|
@ -408,8 +416,7 @@ export class TimerNotif {
|
|||
}
|
||||
|
||||
// CREATE NOTIFICATION
|
||||
const NotificationCompat = androidx.core.app.NotificationCompat
|
||||
const AudioManager = android.media.AudioManager
|
||||
|
||||
let icon = TimerNotif.getIcon(ctx, 'ic_stat_notify_silhouette')
|
||||
let builder = new NotificationCompat.Builder(ctx, cID)
|
||||
.setColor(new Color('#ff5200').android)
|
||||
|
@ -421,6 +428,7 @@ export class TimerNotif {
|
|||
.setSmallIcon(icon)
|
||||
.setTicker(title)
|
||||
.setAutoCancel(false)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
if (sound) builder.setSound(soundUri, AudioManager.STREAM_ALARM)
|
||||
else builder.setSound(null)
|
||||
if (description) builder.setContentText(description)
|
||||
|
@ -428,13 +436,21 @@ export class TimerNotif {
|
|||
if (actions) {
|
||||
builder.setDeleteIntent(actionPInt2)
|
||||
builder.setFullScreenIntent(mainPInt, true)
|
||||
builder.addAction(null, 'Delay', actionPInt1)
|
||||
builder.addAction(null, 'Stop', actionPInt2)
|
||||
builder.addAction(null, 'Stop', actionPInt1)
|
||||
builder.addAction(null, 'Delay', actionPInt2)
|
||||
}
|
||||
let notification = builder.build()
|
||||
notification.flags =
|
||||
NotificationCompat.FLAG_INSISTENT | NotificationCompat.FLAG_ONGOING_EVENT
|
||||
NotifySrv.notify(nID, notification)
|
||||
|
||||
return notification
|
||||
}
|
||||
static show(data) {
|
||||
const ctx = Utils.ad.getApplicationContext()
|
||||
const NotifySrv = ctx.getSystemService(
|
||||
android.content.Context.NOTIFICATION_SERVICE
|
||||
)
|
||||
NotifySrv.notify(data.nID, TimerNotif.getNotification(data, ctx))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
36
package-lock.json
generated
36
package-lock.json
generated
|
@ -1388,9 +1388,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001228",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz",
|
||||
"integrity": "sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A==",
|
||||
"version": "1.0.30001230",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001230.tgz",
|
||||
"integrity": "sha512-5yBd5nWCBS+jWKTcHOzXwo5xzcj4ePE/yjtkZyUV1BTUmrBaA9MRGC+e7mxnqXSA90CmCA8L3eKLaSUkt099IQ==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
|
@ -1877,9 +1877,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.3.738",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.738.tgz",
|
||||
"integrity": "sha512-vCMf4gDOpEylPSLPLSwAEsz+R3ShP02Y3cAKMZvTqule3XcPp7tgc/0ESI7IS6ZeyBlGClE50N53fIOkcIVnpw==",
|
||||
"version": "1.3.739",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.739.tgz",
|
||||
"integrity": "sha512-+LPJVRsN7hGZ9EIUUiWCpO7l4E3qBYHNadazlucBfsXBbccDFNKUBAgzE68FnkWGJPwD/AfKhSzL+G+Iqb8A4A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
|
@ -4718,9 +4718,9 @@
|
|||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "7.4.5",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
|
||||
"integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==",
|
||||
"version": "7.4.6",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
|
||||
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8.3.0"
|
||||
|
@ -5914,9 +5914,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001228",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz",
|
||||
"integrity": "sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A==",
|
||||
"version": "1.0.30001230",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001230.tgz",
|
||||
"integrity": "sha512-5yBd5nWCBS+jWKTcHOzXwo5xzcj4ePE/yjtkZyUV1BTUmrBaA9MRGC+e7mxnqXSA90CmCA8L3eKLaSUkt099IQ==",
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
|
@ -6281,9 +6281,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.738",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.738.tgz",
|
||||
"integrity": "sha512-vCMf4gDOpEylPSLPLSwAEsz+R3ShP02Y3cAKMZvTqule3XcPp7tgc/0ESI7IS6ZeyBlGClE50N53fIOkcIVnpw==",
|
||||
"version": "1.3.739",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.739.tgz",
|
||||
"integrity": "sha512-+LPJVRsN7hGZ9EIUUiWCpO7l4E3qBYHNadazlucBfsXBbccDFNKUBAgzE68FnkWGJPwD/AfKhSzL+G+Iqb8A4A==",
|
||||
"dev": true
|
||||
},
|
||||
"emoji-regex": {
|
||||
|
@ -8386,9 +8386,9 @@
|
|||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
},
|
||||
"ws": {
|
||||
"version": "7.4.5",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
|
||||
"integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==",
|
||||
"version": "7.4.6",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
|
||||
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
|
||||
"dev": true
|
||||
},
|
||||
"xmlbuilder": {
|
||||
|
|
Loading…
Reference in a new issue