Browse Source

๐ŸŽ‰ v1.4.3

- macOS (GPU) critical bug fixed.
- New alert center.
- Portability integration.
- New user improvements.
- DreamTrack integration.
tags/v1.4.4
Ivan Bravo Bravo 8 months ago
parent
commit
35161450e9
59 changed files with 1368 additions and 603 deletions
  1. 1
    1
      .github/workflows/ci.yml
  2. 13
    13
      src/.env-cmdrc.js
  3. 9
    1
      src/assets/css/components/_notification.scss
  4. 20
    20
      src/assets/css/components/_wizard.scss
  5. 77
    14
      src/components/Layout/Navbar.vue
  6. 5
    0
      src/components/Layout/Topbar.vue
  7. 62
    22
      src/components/Nudity/PhotoRun.vue
  8. 32
    19
      src/components/Nudity/Upload.vue
  9. 3
    4
      src/components/Queue/QueuePhoto.vue
  10. 8
    6
      src/components/Settings/Preference.vue
  11. 2
    2
      src/components/Settings/SettingsPreferences.vue
  12. 2
    2
      src/components/UI/AppNews.vue
  13. 3
    2
      src/components/UI/AppUpdate.vue
  14. 2
    2
      src/components/UI/BoxItem.vue
  15. 0
    126
      src/components/UI/BoxSectionItem.vue
  16. 7
    4
      src/components/UI/ProjectUpdate.vue
  17. 1
    1
      src/components/UI/index.js
  18. 43
    7
      src/electron/src/index.js
  19. 1
    1
      src/electron/src/modules/ngrok.js
  20. 7
    3
      src/electron/src/modules/settings.js
  21. 44
    18
      src/electron/src/modules/tools/paths.js
  22. 6
    4
      src/electron/src/modules/tools/system.js
  23. 2
    0
      src/middleware/wizard.js
  24. 2
    6
      src/mixins/UploadMixin.js
  25. 5
    5
      src/modules/config/cli-errors.json
  26. 4
    6
      src/modules/consola/consola.js
  27. 65
    35
      src/modules/consola/log.js
  28. 7
    0
      src/modules/dream.js
  29. 14
    6
      src/modules/file.js
  30. 40
    3
      src/modules/nudify/photo.js
  31. 6
    4
      src/modules/services/base.js
  32. 279
    0
      src/modules/services/dreamtrack.js
  33. 2
    1
      src/modules/services/index.js
  34. 7
    5
      src/modules/services/logrocket.js
  35. 6
    12
      src/modules/services/rollbar.js
  36. 71
    22
      src/modules/system/requirements.js
  37. 67
    34
      src/modules/tutorial.js
  38. 11
    10
      src/modules/updater/base.js
  39. 8
    4
      src/modules/updater/dreampower.js
  40. 0
    8
      src/nuxt.config.js
  41. 7
    3
      src/package.json
  42. 1
    1
      src/package.min.json
  43. 99
    15
      src/pages/about.vue
  44. 77
    0
      src/pages/alerts.vue
  45. 3
    3
      src/pages/dreamnet.vue
  46. 5
    5
      src/pages/index.vue
  47. 37
    9
      src/pages/nudify/_id.vue
  48. 7
    2
      src/pages/nudify/_id/editor.vue
  49. 5
    0
      src/pages/nudify/_id/preferences.vue
  50. 11
    12
      src/pages/nudify/_id/results.vue
  51. 89
    6
      src/pages/settings/folders.vue
  52. 4
    3
      src/pages/settings/preferences.vue
  53. 0
    62
      src/pages/welcome.vue
  54. 14
    14
      src/pages/wizard/checkpoints.vue
  55. 37
    21
      src/pages/wizard/power.vue
  56. 1
    3
      src/pages/wizard/telemetry.vue
  57. 2
    2
      src/patches/tui-image-editor+3.7.3.patch
  58. 7
    4
      src/plugins/boot.js
  59. 18
    5
      src/scripts/release.js

+ 1
- 1
.github/workflows/ci.yml View File

@@ -77,7 +77,7 @@ jobs:
working-directory: src
run: yarn lint

- name: Build
- name: Build (Installer)
working-directory: src
env:
GITHUB_SHA: ${{ github.sha }}

+ 13
- 13
src/.env-cmdrc.js View File

@@ -1,30 +1,30 @@
const development = {
"LOG": "debug",
"DEVTOOLS": true,
}

module.exports = {
"default": {
"SERVER_HOST": "localhost",
"SERVER_PORT": 4000,
"NUCLEUS_APPID": "5d353cecbe5ccc0133cf90f4"
"SERVER_PORT": 4000
},
"development": {
"name": "development",
"NODE_ENV": "development",
"LOG": "debug",
"DEVTOOLS": true,
"ROLLBAR_ACCESS_TOKEN": "6ccfcf317ca54e67830b41570ce23d2a",
"LOGROCKET_ACCESS_TOKEN": "of2lox/dreamtime"
"ROLLBAR_ACCESS_TOKEN": "e62c909ec771492fa7f371dc61eea092",
"LOGROCKET_ACCESS_TOKEN": "of2lox/dreamtime",
"DREAMTRACK_HOST": "track.dreamnet.tech",
...development
},
"production": {
"name": "production",
"NODE_ENV": "production",
"LOG": "info"
"LOG": "info",
"DREAMTRACK_HOST": "track.dreamnet.tech"
},
"test": {
"name": "test",
"NODE_ENV": "test",
"LOG": "debug"
},
"preview": {
"DEVTOOLS": true,
"ROLLBAR_ACCESS_TOKEN": "6ccfcf317ca54e67830b41570ce23d2a",
"LOGROCKET_ACCESS_TOKEN": "of2lox/dreamtime"
...development
}
}

+ 9
- 1
src/assets/css/components/_notification.scss View File

@@ -11,7 +11,7 @@

.notification {
@apply mb-4 py-2 px-4 border-2 border-dark-100 rounded-sm;
@apply bg-transparent text-generic-100;
@apply bg-transparent text-generic-500 text-sm;

a {
@apply underline;
@@ -24,4 +24,12 @@
&.notification--danger {
@apply text-danger-400 border-danger;
}

&.notification--success {
@apply text-success-400 border-success;
}

.icon {
@apply mr-2;
}
}

+ 20
- 20
src/assets/css/components/_wizard.scss View File

@@ -13,6 +13,26 @@
.project__content {
@apply flex mb-6 pb-6 border-b border-dark-500;

.project__update {
@apply flex-1;
}

.project__description {
@apply flex-1 flex flex-col justify-center;

p {
@apply text-xl mb-6;
}

a {
@apply text-white underline;
}
}
}

.project__installation {
@apply flex;

.project__overview {
@apply flex-1 flex flex-col justify-center items-center;

@@ -43,26 +63,6 @@
}
}

.project__description {
@apply flex-1 flex flex-col justify-center;

p {
@apply text-xl mb-6;
}

a {
@apply text-white underline;
}
}
}

.project__installation {
@apply flex;

.project__update {
@apply flex-1;
}

.project__settings {
@apply flex-1;
}

+ 77
- 14
src/components/Layout/Navbar.vue View File

@@ -1,37 +1,57 @@
<template>
<div class="layout__navbar">
<div class="navbar__left">
<nuxt-link v-if="canNudify" to="/" class="navbar__item">
Nudify
<nuxt-link v-if="canNudify" to="/" class="navbar__item navbar__item--home">
Upload
</nuxt-link>

<nuxt-link id="settings" class="navbar__item" to="/settings">
Settings
</nuxt-link>

<nuxt-link v-if="unlockedBadTime" class="navbar__item" to="/games/badtime">
Bad Time
<nuxt-link v-if="unlockedBadTime" v-tooltip="{ content: 'Mini-game. Can you survive until the end? ๐ŸŽฎ๐Ÿ’€', placement: 'bottom' }" class="navbar__item" to="/games/badtime">
Bad Time Game
</nuxt-link>

<a id="guide" class="navbar__item" :href="manualURL" target="_blank">
Help
</a>

<a v-if="isDev" class="navbar__item" @click.prevent="createError">
Force Error
</a>
</div>

<div class="navbar__right">
<nuxt-link v-tooltip="{placement: 'bottom', content: 'Alert Center'}" class="navbar__icon" to="/alerts">
<font-awesome-icon v-if="hasAlerts" icon="exclamation-triangle" class="alerts--active" />
<font-awesome-icon v-else icon="check-circle" class="alerts--ok" />
</nuxt-link>

<nuxt-link v-tooltip="{placement: 'bottom', content: 'About'}" class="navbar__icon" to="/about">
<font-awesome-icon icon="info-circle" />
</nuxt-link>

<nuxt-link v-tooltip="{placement: 'bottom', content: 'DreamNet'}" class="navbar__icon" to="/dreamnet">
<nuxt-link
v-if="$provider.system.online"
v-tooltip="{placement: 'bottom', content: 'DreamNet'}"
class="navbar__icon"
to="/dreamnet">
<font-awesome-icon icon="users" />
</nuxt-link>

<a v-tooltip="{placement: 'bottom', content: 'Donate and get benefits!'}" class="navbar__icon" :href="donateUrl" target="_blank">
<a
v-if="$provider.system.online"
id="guide"
v-tooltip="{placement: 'bottom', content: 'User\'s Guide.'}"
class="navbar__icon"
:href="manualURL"
target="_blank">
<font-awesome-icon icon="question-circle" />
</a>

<a
v-if="$provider.system.online"
v-tooltip="{placement: 'bottom', content: 'Donate and get benefits!'}"
class="navbar__icon"
:href="donateUrl"
target="_blank">
<font-awesome-icon :icon="['fab', 'patreon']" />
</a>
</div>
@@ -40,7 +60,7 @@

<script>
import { requirements, settings } from '~/modules/system'
import { nucleus } from '~/modules/services'
import { dreamtrack } from '~/modules/services'
import { events } from '~/modules'

export default {
@@ -54,15 +74,19 @@ export default {
},

donateUrl() {
return nucleus.urls?.support?.patreon || 'https://www.patreon.com/dreamnet'
return dreamtrack.get('urls.support.patreon', 'https://www.patreon.com/dreamnet')
},

manualURL() {
return nucleus.urls?.docs?.manual || 'https://time.dreamnet.tech/docs/guide/upload'
return dreamtrack.get('urls.docs.manual', 'https://time.dreamnet.tech/docs/guide/upload')
},

isDev() {
return process.env.name === 'development'
return process.env.NODE_ENV === 'development'
},

hasAlerts() {
return requirements.hasAlerts
},
},

@@ -81,6 +105,18 @@ export default {
</script>

<style lang="scss" scoped>
@keyframes alertAnim {
0% {
@apply text-danger-500;
}
50% {
@apply text-warning-500;
}
100% {
@apply text-danger-500;
}
}

.layout__navbar {
@apply flex bg-dark-500 z-10;
@apply border-b border-dark-100;
@@ -105,6 +141,18 @@ export default {
&:hover {
@apply text-white;
}

&:not(.navbar__item--home) {
&.nuxt-link-active {
@apply text-primary-500;
}
}

&.navbar__item--home {
&.nuxt-link-exact-active {
@apply text-primary-500;
}
}
}

.navbar__icon {
@@ -113,6 +161,21 @@ export default {
&:hover {
@apply text-white;
}

&.nuxt-link-active {
@apply text-primary-500;
}
}
}

.alerts--active {
animation-name: alertAnim;
animation-iteration-count: infinite;
animation-duration: 3s;
animation-timing-function: ease-in-out;
}

.alerts--ok {
@apply text-success-500;
}
</style>

+ 5
- 0
src/components/Layout/Topbar.vue View File

@@ -135,6 +135,11 @@ export default {
);

background-size: 200% 100%;

animation-name: bgAnim;
animation-iteration-count: infinite;
animation-duration: 10s;
animation-timing-function: ease-in-out;
}

.topbar__greetings {

+ 62
- 22
src/components/Nudity/PhotoRun.vue View File

@@ -2,7 +2,9 @@
<div class="photo-run" :class="previewClass" data-private>
<div class="run__preview" :style="previewStyle" />

<div v-if="run.preferences.body.randomize || run.preferences.body.progressive.enabled" class="run__preferences">
<div
v-if="run.preferences.body.randomize || run.preferences.body.progressive.enabled"
class="run__preferences">
<div class="preference">
<span>Boobs</span>
<span>{{ run.preferences.body.boobs.size | fixedValue }}</span>
@@ -32,39 +34,59 @@
<div class="run__content">
<div v-if="run.running" class="content__item">
<p class="text-white">
<span><font-awesome-icon icon="running" /></span>
<span>
<font-awesome-icon icon="running" />
</span>
<span>{{ run.timer.duration }}s</span>
</p>
</div>

<div v-else-if="run.failed" class="content__item">
<p class="text-danger-500">
<span><font-awesome-icon icon="exclamation-circle" /></span>
<span>
<font-awesome-icon icon="exclamation-circle" />
</span>
<span>Error!</span>
</p>
</div>

<div v-else-if="run.finished" class="content__item">
<p class="text-white">
<span><font-awesome-icon icon="heart" /></span>
<span>
<font-awesome-icon icon="heart" />
</span>
<span>{{ run.timer.duration }}s</span>
</p>
</div>

<div v-else class="content__item">
<p class="text-white">
<span><font-awesome-icon icon="clock" /></span>
<span>
<font-awesome-icon icon="clock" />
</span>
</p>
</div>

<div v-show="run.finished && run.outputFile.exists" class="content__item">
<button v-tooltip="'Save photo'" class="button button--success button--sm" @click.prevent="save">
<button
v-tooltip="'Open photo'"
class="button button--info button--sm"
@click.prevent="open">
<font-awesome-icon icon="image" />
</button>
</div>

<div v-show="run.finished && run.outputFile.exists" class="content__item">
<button
v-tooltip="'Save photo'"
class="button button--info button--sm"
@click.prevent="save">
<font-awesome-icon icon="save" />
</button>
</div>

<div v-show="run.finished" class="content__item">
<button v-tooltip="'Rerun'" class="button button--danger button--sm" @click.prevent="rerun">
<button v-tooltip="'Rerun'" class="button button--success button--sm" @click.prevent="rerun">
<font-awesome-icon icon="undo" />
</button>
</div>
@@ -76,13 +98,19 @@
</div>

<div v-show="hasMaskfin" class="content__item">
<button v-tooltip="'View Maskfin'" class="button button--info button--sm" @click.prevent="$refs.maskfinDialog.showModal()">
<button
v-tooltip="'View Maskfin'"
class="button button--sm"
@click.prevent="$refs.maskfinDialog.showModal()">
<font-awesome-icon icon="mask" />
</button>
</div>

<div class="content__item">
<button v-tooltip="'View terminal'" class="button button--sm" @click.prevent="$refs.terminalDialog.showModal()">
<button
v-tooltip="'View terminal'"
class="button button--sm"
@click.prevent="$refs.terminalDialog.showModal()">
<font-awesome-icon icon="terminal" />
</button>
</div>
@@ -98,7 +126,10 @@
<div class="maskfin__description">
<p>This is the Maskfin, a mask that represents in layers the areas that the algorithm will replace with the fake nude.</p>
<p>Click on the "Add to queue" button to add it as an additional photo, edit the layers with the editor and continue with the nudification. You can also save it to your computer, edit it with an external program and continue the nudification manually.</p>
<p>For more information please consult the <a :href="manualURL" target="_blank">guide</a>.</p>
<p>
For more information please consult the
<a :href="manualURL" target="_blank">guide</a>.
</p>
</div>

<div class="dialog__buttons">
@@ -121,7 +152,10 @@
<dialog ref="terminalDialog">
<div class="dialog__content">
<div class="terminal">
<li v-for="(item, index) in run.cli.lines" :key="index" :class="item.css">
<li
v-for="(item, index) in run.cli.lines"
:key="index"
:class="item.css">
> {{ item.text }}
</li>
</div>
@@ -137,12 +171,9 @@
</template>

<script>
import { isNil } from 'lodash'
import { nucleus } from '~/modules/services'
import { dreamtrack } from '~/modules/services'
import { Nudify } from '~/modules/nudify'

const { showSaveDialogSync } = $provider.api.dialog

export default {
filters: {
size(value) {
@@ -185,7 +216,10 @@ export default {
},

manualURL() {
return nucleus.urls?.docs?.manual || 'https://time.dreamnet.tech/docs/guide/upload'
return dreamtrack.get(
'urls.docs.manual',
'https://time.dreamnet.tech/docs/guide/upload',
)
},
},

@@ -194,6 +228,10 @@ export default {
this.run.outputFile.save(this.run.outputName)
},

open() {
this.run.outputFile.openItem()
},

rerun() {
this.run.add()
},
@@ -215,10 +253,10 @@ export default {

<style lang="scss" scoped>
.photo-run {
@apply relative border-2 border-dark-100;
background-image: url('~@/assets/images/curls.png'); /* Background pattern from Toptal Subtle Patterns */
@apply relative border-2 border-dark-500;
background-image: url("~@/assets/images/curls.png"); /* Background pattern from Toptal Subtle Patterns */
min-height: 512px;
transition: border-color .2s linear;
transition: border-color 0.2s linear;

&.run--failed {
@apply border-danger-500;
@@ -235,6 +273,8 @@ export default {
}

&:hover {
@apply border-primary-300;

.run__content,
.run__preferences {
@apply opacity-100;
@@ -244,16 +284,16 @@ export default {
.run__preview {
@apply absolute opacity-0 left-0 right-0 top-0 bottom-0 z-10;
@apply bg-contain bg-center bg-no-repeat;
transition: opacity .3s linear;
transition: opacity 0.3s linear;
}
}

.run__content,
.run__preferences {
@apply absolute z-20;
@apply flex opacity-0 bg-dark-500-80 w-full;
@apply flex opacity-0 bg-dark-800-80 w-full;
backdrop-filter: blur(6px);
transition: opacity .1s linear;
transition: opacity 0.1s linear;
}

.run__preferences {

+ 32
- 19
src/components/Nudity/Upload.vue View File

@@ -21,13 +21,6 @@

<div id="uploader-methods" class="box box--items">
<div class="box__content">
<box-item
label="Web"
icon="globe"
:is-link="true"
:class="{'box__item--active': selectionId === 0}"
@click="selectionId = 0" />

<box-item
label="Instagram"
:icon="['fab', 'instagram']"
@@ -35,6 +28,13 @@
:class="{'box__item--active': selectionId === 1}"
@click="selectionId = 1" />

<box-item
label="Web"
icon="globe"
:is-link="true"
:class="{'box__item--active': selectionId === 0}"
@click="selectionId = 0" />

<box-item
label="File"
icon="file"
@@ -72,11 +72,12 @@
<input v-model="webAddress" type="url" class="input mb-2" placeholder="https://" data-private="lipsum">

<p class="help">
It must end in a valid extension (jpg, png, gif)
Enter the web address of a photo that ends in a valid extension. <i>(jpg, png, gif)</i>
</p>

<button class="button" @click="openUrl">
Submit
<span class="icon"><font-awesome-icon icon="globe" /></span>
<span>Submit</span>
</button>
</div>

@@ -85,11 +86,12 @@
<input v-model="instagramPhoto" type="url" class="input mb-2" placeholder="https://www.instagram.com/p/dU4fHDw-Ho/" data-private="lipsum">

<p class="help">
Enter the web address or Media ID of an Instagram photo.
Enter the web address or ID of an Instagram photo.
</p>

<button class="button" @click="openInstagramPhoto">
Submit
<span class="icon"><font-awesome-icon :icon="['fab', 'instagram']" /></span>
<span>Submit</span>
</button>
</div>

@@ -119,7 +121,7 @@
</button>

<p class="help">
Select one or more folders on your computer. All valid photos inside will be uploaded.
Select a folder from your computer. All valid photos inside will be uploaded.
</p>
</div>
</div>
@@ -129,7 +131,7 @@

<script>
import {
isEmpty, startsWith, map,
isEmpty, startsWith, map, toNumber,
} from 'lodash'
import { Nudify } from '~/modules/nudify'
import { tutorial } from '~/modules'
@@ -148,11 +150,22 @@ export default {
},

data: () => ({
selectionId: 0,
selectionId: 1,
webAddress: '',
instagramPhoto: '',
}),

watch: {
selectionId(value) {
localStorage.uploadSelectionId = value
},
},

created() {
this.selectionId = localStorage.uploadSelectionId || 1
this.selectionId = toNumber(this.selectionId)
},

mounted() {
tutorial.upload()
},
@@ -170,7 +183,7 @@ export default {

const paths = map(files, 'path')

consola.track('FILE')
consola.track('UPLOAD_FILE')

this.addFiles(paths)

@@ -187,7 +200,7 @@ export default {

Nudify.addUrl(this.webAddress)

consola.track('URL')
consola.track('UPLOAD_URL')

this.webAddress = ''
},
@@ -214,7 +227,7 @@ export default {

Nudify.addUrl(post.downloadUrl)

consola.track('INSTAGRAM')
consola.track('UPLOAD_INSTAGRAM')

this.instagramPhoto = ''
},
@@ -244,7 +257,7 @@ export default {

.selection__content__body {
@apply text-center;
width: 50%;
width: 70%;
}

.input {
@@ -252,7 +265,7 @@ export default {
}

.help {
@apply text-sm;
@apply text-sm text-generic-700;

&:not(:last-child) {
@apply mb-4;

+ 3
- 4
src/components/Queue/QueuePhoto.vue View File

@@ -9,7 +9,7 @@
<font-awesome-icon icon="external-link-square-alt" />
</button>

<button v-show="!photo.running && !photo.waiting" v-tooltip="'Add to Queue'" @click="add">
<button v-show="photo.pending" v-tooltip="'Add to Queue'" @click="add">
<font-awesome-icon icon="play" />
</button>

@@ -80,7 +80,8 @@ export default {

<style lang="scss" scoped>
.photo {
@apply w-1/2 relative border-2 border-transparent;
@apply w-1/2 relative border-2 border-dark-300;
background-image: url("~@/assets/images/curls.png");
height: 150px;
will-change: transform;

@@ -93,8 +94,6 @@ export default {
}

&:hover {
@apply bg-dark-900;

.photo__content {
@apply opacity-100;
}

+ 8
- 6
src/components/Settings/Preference.vue View File

@@ -2,13 +2,15 @@
<section class="box box--items">
<div class="box__content">
<box-item :description="`Value: ${currentValue.size}`" :label="`${label} size`">
<VueSlider v-model="currentValue.size" :min="min" :max="max" :interval="0.05" />
<div :style="{ opacity: body.randomize ? 0.3 : 1.0 }">
<VueSlider v-model="currentValue.size" :min="min" :max="max" :interval="0.05" />
</div>
</box-item>

<box-item
v-show="!body.randomize && body.progressive.enabled"
label="Progressive."
description="Increase the value progressively in each run">
label="Progressive?"
description="Increase this body part progressively in each run.">
<select v-model="currentValue.progressive" class="input">
<option :value="true">
Enabled
@@ -21,8 +23,8 @@

<div v-show="body.randomize">
<box-item
label="Randomize."
description="Randomize the value in each run.">
label="Randomize?"
description="Randomize this body part in each run.">
<select v-model="currentValue.randomize.enabled" class="input">
<option :value="true">
Enabled
@@ -34,7 +36,7 @@
</box-item>

<box-item
label="Range."
label="Random range"
:description="`Min: ${currentValue.randomize.min} - Max: ${currentValue.randomize.max}`">
<VueSlider
v-model="randomizeRange"

+ 2
- 2
src/components/Settings/SettingsPreferences.vue View File

@@ -83,7 +83,7 @@
<box-item
id="preferences-advanced-scale"
label="Scale method."
description="Method that will be used to scale the photo. Each option can offer different results in each photo.">
description="Indicates how the photo will be scaled, this changes the quality of the result dramatically.">
<select v-model="currentValue.advanced.scaleMode" class="input">
<option value="none">
None
@@ -101,7 +101,7 @@
Overlay
</option>
<option value="cropjs">
Manual crop
Crop
</option>
</select>
</box-item>

+ 2
- 2
src/components/UI/AppNews.vue View File

@@ -13,12 +13,12 @@
</template>

<script>
import { nucleus } from '~/modules/services'
import { dreamtrack } from '~/modules/services'

export default {
computed: {
twitterUrl() {
return nucleus.urls?.social?.twitter || 'https://twitter.com/DreamNetTechno'
return dreamtrack.get('urls.social.twitter', 'https://twitter.com/DreamNetTechno')
},
},
}

+ 3
- 2
src/components/UI/AppUpdate.vue View File

@@ -2,15 +2,16 @@
<!-- Cannot update, only show the version... -->
<box-item
v-if="!updater.enabled"
v-tooltip="{ content: 'Installed version', placement: 'bottom' }"
:label="currentVersion"
icon="globe" />
icon="rocket" />

<!-- Updated! -->
<box-item
v-else-if="!updater.available"
:label="`${projectTitle} is up to date.`"
:description="currentVersion"
icon="globe" />
icon="rocket" />

<!-- Update available -->
<box-item

+ 2
- 2
src/components/UI/BoxItem.vue View File

@@ -26,7 +26,7 @@

<script>
import { isNil, startsWith } from 'lodash'
import { nucleus } from '~/modules/services'
import { dreamtrack } from '~/modules/services'
import { dream } from '~/modules'

const { shell } = $provider.api
@@ -92,7 +92,7 @@ export default {
if (startsWith(this.href, '/')) {
this.$router.push(this.href)
} else {
nucleus.track('EXTERNAL_LINK', { href: this.href })
dreamtrack.track('CLICK_LINK', { href: this.href })
shell.openExternal(this.href)
}
}

+ 0
- 126
src/components/UI/BoxSectionItem.vue View File

@@ -1,126 +0,0 @@
<template>
<itemtag :version="version" :tag="tag" :href="href" class="box-section-item">
<slot name="icon">
<figure v-if="isURL(icon)" class="item-icon">
<img :src="icon">
</figure>

<span v-else-if="icon" class="item-icon">{{ icon }}</span>
</slot>

<div class="item-text">
<p class="item-label">
{{ label }}
</p>

<slot name="description">
<p v-if="description" class="item-description">
{{ description }}
</p>
</slot>
</div>

<div class="item-extra">
<slot />
</div>
</itemtag>
</template>

<script>
import _ from 'lodash'

export default {
components: {
itemtag: {
name: 'itemtag',
props: ['tag', 'href', 'version'],
computed: {
isVisible() {
if (_.isNil(this.version)) {
return true
}

return this.version === this.$dream.version
},
},
render(createElement) {
if (!this.isVisible) {
return null
}

return createElement(
this.tag,
{
props: {
href: this.href,
},
},
this.$slots.default,
)
},
},
},

props: {
label: {
type: String,
required: true,
},

description: {
type: String,
default: '',
},

icon: {
type: String,
default: undefined,
},

href: {
type: String,
default: undefined,
},

version: {
type: String,
default: undefined,
},
},

computed: {
tag() {
let tag = 'div'

if (!_.isNil(this.href)) {
tag = 'app-external-link'
}

return tag
},
},

methods: {
isURL(str) {
if (_.isNil(str)) {
return false
}

if (_.startsWith(str, '~')) {
return true
}

const pattern = new RegExp(
'^(https?:\\/\\/)?' // protocol
+ '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' // domain name
+ '((\\d{1,3}\\.){3}\\d{1,3}))' // OR ip (v4) address
+ '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' // port and path
+ '(\\?[;&a-z\\d%_.~+=-]*)?' // query string
+ '(\\#[-a-z\\d_]*)?$',
'i',
) // fragment locator
return !!pattern.test(str)
},
},
}
</script>

+ 7
- 4
src/components/UI/ProjectUpdate.vue View File

@@ -1,8 +1,11 @@
<template>
<div class="project-update">
<!-- Update available -->
<div v-if="!updater.update.active" class="update__status">
{{ updater.latest.tag_name }} available:
<div
v-if="!updater.update.active"
v-tooltip="'New version'"
class="update__status">
<strong>{{ updater.latest.tag_name }}</strong>
</div>

<!-- Downloading -->
@@ -23,14 +26,14 @@
<!-- Actions -->
<div class="update__actions">
<button v-show="!updater.update.active" class="button button--success" @click.prevent="updater.start()">
Download
Start
</button>

<button v-show="updater.update.active" class="button button--danger" @click.prevent="updater.cancel()">
Cancel
</button>

<button v-tooltip="'Show a list of links to download the update and install it manually.'" class="button button--info" @click.prevent="$refs.mirrorsDialog.show()">
<button v-tooltip="'Show a list of links to download the update manually.'" class="button button--info" @click.prevent="$refs.mirrorsDialog.show()">
Mirrors
</button>
</div>

+ 1
- 1
src/components/UI/index.js View File

@@ -10,9 +10,9 @@
import Vue from 'vue'
import Title from './AppTitle'
import Update from './AppUpdate'
import BoxItem from './BoxItem'
import AppPhoto from './AppPhoto'
import AppNews from './AppNews'
import BoxItem from './BoxItem'
import ProjectUpdate from './ProjectUpdate'

Vue.component('app-title', Title)

+ 43
- 7
src/electron/src/index.js View File

@@ -14,7 +14,9 @@ import Logger from '@dreamnet/logplease'
import fs from 'fs-extra'
import { AppError } from './modules/app-error'
import { system } from './modules/tools/system'
import { getPath } from './modules/tools/paths'
import {
getPath, getModelsPath, getMasksPath, getAppPath,
} from './modules/tools/paths'
import { settings, ngrok } from './modules'
import config from '~/nuxt.config'

@@ -23,7 +25,7 @@ const logger = Logger.create('electron')
// NuxtJS root directory
config.rootDir = dirname(dirname(__dirname))

if (process.env.name === 'production') {
if (process.env.NODE_ENV === 'production') {
// make sure that the working directory is where the executable is
process.chdir(getPath('exe', '..'))
}
@@ -49,7 +51,8 @@ class DreamApp {

logger.info('Booting...')

logger.debug(`Enviroment: ${process.env.name}`)
logger.debug(`Enviroment: ${process.env.NODE_ENV}`)
logger.debug(`Portable: ${process.env.BUILD_PORTABLE}`)
logger.debug(`App Path: ${app.getAppPath()}`)
logger.debug(`Exe Path: ${app.getPath('exe')}`)

@@ -74,6 +77,10 @@ class DreamApp {
// https://pracucci.com/electron-slow-background-performances.html
app.commandLine.appendSwitch('disable-renderer-backgrounding')

if (process.env.BUILD_PORTABLE) {
this.bootPortable()
}

// user settings.
await settings.boot()

@@ -84,6 +91,32 @@ class DreamApp {
}
}

/**
*
*/
static bootPortable() {
// Portable component files
fs.ensureDirSync(getAppPath('AppData'))

const settingsPath = getPath('userData', 'settings.json')
const portableSettingsPath = getAppPath('AppData', 'settings.json')

const powerPath = getPath('userData', 'dreampower')
const portablePowerPath = getAppPath('AppData', 'dreampower')

try {
if (fs.existsSync(settingsPath)) {
fs.moveSync(settingsPath, portableSettingsPath)
}

if (fs.existsSync(powerPath)) {
fs.moveSync(powerPath, portablePowerPath)
}
} catch (error) {
logger.warn('Portable boot fail!', error)
}
}

/**
* Start the app!
*/
@@ -104,6 +137,9 @@ class DreamApp {

// https://github.com/sindresorhus/electron-util#enforcemacosapplocation-macos
enforceMacOSAppLocation()

// PyTorch does not have support for GPU in macOS
settings.processing.device = 'CPU'
}

// application exit.
@@ -175,7 +211,7 @@ class DreamApp {
this.createDirs()

/*
if (process.env.name === 'development') {
if (process.env.NODE_ENV === 'development') {
const address = await ngrok.connect()
logger.debug(`Proxy for debugging: ${address}`)
}
@@ -189,7 +225,7 @@ class DreamApp {
static async shutdown() {
logger.debug('Shutting down services...')

if (process.env.name === 'development') {
if (process.env.NODE_ENV === 'development') {
await ngrok.disconnect()
}
}
@@ -280,8 +316,8 @@ class DreamApp {
*/
static createDirs() {
const dirs = [
resolve(settings.folders.models, 'Uncategorized'),
settings.folders.masks,
getModelsPath('Uncategorized'),
getMasksPath(),
]

dirs.forEach((dir) => {

+ 1
- 1
src/electron/src/modules/ngrok.js View File

@@ -12,7 +12,7 @@ import { isNil } from 'lodash'
let address

export async function connect() {
if (process.env.name !== 'development') {
if (process.env.NODE_ENV !== 'development') {
return null
}


+ 7
- 3
src/electron/src/modules/settings.js View File

@@ -39,6 +39,10 @@ class Settings {
* @type {string}
*/
get path() {
if (process.env.BUILD_PORTABLE) {
return paths.getAppPath('AppData', 'settings.json')
}

return paths.getPath('userData', 'settings.json')
}

@@ -112,7 +116,7 @@ class Settings {
*/
_loadDefault() {
const uuid = require('uuid')
const hasGPU = system.graphics.length > 0
const hasGPU = process.platform === 'darwin' ? false : system.graphics.length > 0
const cores = round(system.cpu?.cores / 2) || 1

this.payload = {
@@ -144,7 +148,7 @@ class Settings {

folders: {
cropped: paths.getPath('temp'),
models: paths.getPath('pictures', 'DreamTime'),
models: paths.getPath('userData', 'Pictures'),
masks: paths.getPath('temp'),
cli: paths.getPath('userData', 'dreampower'),
},
@@ -158,7 +162,7 @@ class Settings {
device: hasGPU ? 'GPU' : 'CPU',
gpus: [0],
cores,
usePython: process.env.NODE_ENV === 'development',
usePython: false,
},

preferences: {

+ 44
- 18
src/electron/src/modules/tools/paths.js View File

@@ -28,8 +28,6 @@ export function getPath(name, ...args) {
* @param {...string} args
*/
export const getAppPath = (...args) => {
// return join(app.getAppPath(), ...args)

if (process.platform === 'darwin') {
// /Applications/DreamTime.app/Contents/MacOS/DreamTime
// /Applications/DreamTime.app/Contents
@@ -48,10 +46,12 @@ export const getAppResourcesPath = (...args) => {
}

export const getPowerPath = (...args) => {
let folder = settings.folders.cli
let folder

if (!fs.existsSync(folder)) {
folder = getPath('userData', 'dreampower')
if (process.env.BUILD_PORTABLE) {
folder = getAppPath('AppData', 'dreampower')
} else {
folder = settings.folders.cli
}

return join(folder, ...args)
@@ -60,24 +60,40 @@ export const getPowerPath = (...args) => {
export const getCheckpointsPath = (...args) => getPowerPath('checkpoints', ...args)

export const getCropPath = (...args) => {
let folder = settings.folders.cropped
let folder

attempt(() => fs.ensureDirSync(folder))

if (!fs.existsSync(folder)) {
if (process.env.BUILD_PORTABLE) {
folder = getPath('temp')

if (!fs.existsSync(folder)) {
folder = getAppPath('AppData', 'Temp')
}
} else {
folder = settings.folders.cropped

if (!fs.existsSync(folder)) {
folder = getPath('temp')
}
}

attempt(() => fs.ensureDirSync(folder))

return join(folder, ...args)
}

export const getModelsPath = (...args) => {
let folder = settings.folders.models
let folder

if (process.env.BUILD_PORTABLE) {
folder = getAppPath('AppData', 'Pictures')
} else {
folder = settings.folders.models
}

attempt(() => fs.ensureDirSync(folder))

if (!fs.existsSync(folder)) {
folder = getPath('userData', 'models')
folder = getPath('userData', 'Pictures')
}

if (!fs.existsSync(folder)) {
@@ -88,17 +104,27 @@ export const getModelsPath = (...args) => {
}

export const getMasksPath = (...args) => {
let folder = settings.folders.masks
let folder

attempt(() => fs.ensureDirSync(folder))
if (process.env.BUILD_PORTABLE) {
folder = getPath('temp')

if (!fs.existsSync(folder)) {
folder = getPath('userData', 'masks')
}
if (!fs.existsSync(folder)) {
folder = getAppPath('AppData', 'Temp')
}
} else {
folder = settings.folders.masks

if (!fs.existsSync(folder)) {
folder = getPath('temp')
if (!fs.existsSync(folder)) {
folder = getPath('userData', 'masks')
}

if (!fs.existsSync(folder)) {
folder = getPath('temp')
}
}

attempt(() => fs.ensureDirSync(folder))

return join(folder, ...args)
}

+ 6
- 4
src/electron/src/modules/tools/system.js View File

@@ -12,6 +12,8 @@ import {
} from 'lodash'
import si from 'systeminformation'
import isOnline from 'is-online'
import { settings } from '../settings'


const logger = require('@dreamnet/logplease').create('system')

@@ -91,12 +93,11 @@ class System {
async takeSnapshot() {
logger.info('Taking snapshot...')

const [load, cpuSpeed, cpuTemperature, memory, online] = await Promise.all([
const [load, cpuSpeed, cpuTemperature, memory] = await Promise.all([
si.currentLoad(),
si.cpuCurrentspeed(),
si.cpuTemperature(),
si.mem(),
isOnline(),
])

this.snapshot = {
@@ -106,14 +107,15 @@ class System {
temperature: cpuTemperature,
},
memory,
online,
settings: settings.payload,
}

logger.info(`Current load:`, load)
logger.info(`CPU Speed:`, cpuSpeed)
logger.info(`CPU Temperature:`, cpuTemperature)
logger.info(`Memory:`, memory)
logger.info(`Online: ${online}`)

return this.snapshot
}

/**

+ 2
- 0
src/middleware/wizard.js View File

@@ -8,12 +8,14 @@
// Written by Ivan Bravo Bravo <ivan@dreamnet.tech>, 2019.

import { settings, requirements } from '~/modules/system'
import { consola } from '~/modules/consola'

export default function ({ route, redirect }) {
const { wizard } = settings

if (!wizard.welcome) {
if (route.path !== '/wizard/welcome') {
consola.track('WELCOME')
redirect('/wizard/welcome')
}


+ 2
- 6
src/mixins/UploadMixin.js View File

@@ -61,6 +61,8 @@ export const UploadMixin = {
properties: ['openDirectory'],
})

consola.track('UPLOAD_FOLDER')

this.addFiles(paths)
},

@@ -110,12 +112,6 @@ export const UploadMixin = {
const { files } = event.dataTransfer
const url = event.dataTransfer.getData('url')

consola.debug('onDrop', {
event,
files,
url,
})

if (url.length > 0) {
Nudify.addUrl(url)
consola.track('DROP_URL')

+ 5
- 5
src/modules/config/cli-errors.json View File

@@ -53,11 +53,7 @@
},
{
"query": "cv2.error",
"message": "There was a problem trying to load the photo. Please ensure that it is a valid photo, that it has not been deleted or that the Cropper/Overlay are well located."
},
{
"query": "OpenCV",
"message": "There was a problem trying to load the photo. Please ensure that it is a valid photo, that it has not been deleted or that the Cropper/Overlay are well located."
"message": "There was a problem trying to load the photo. Please ensure that it is a valid photo, that it has not been deleted or that the Cropper/Overlay are well positioned."
},
{
"query": "is not a valid file or directory or url",
@@ -71,6 +67,10 @@
"query": "invalid extension format",
"message": "There was a problem trying to load the photo. Please make sure it is a valid photo and has not been deleted."
},
{
"query": "The file might be corrupted",
"message": "There was a problem trying to load the photo. Please make sure it is a valid photo and has not been deleted."
},
{
"query": "not compiled with CUDA",
"message": "You have installed the CPU-only version of DreamPower. Please reinstall DreamPower to get GPU support."

+ 4
- 6
src/modules/consola/consola.js View File

@@ -93,16 +93,14 @@ export class Consola {
* @param {Object} payload
*/
track(event, payload = {}) {
const { nucleus, logrocket } = require('../services')
const { dreamtrack, logrocket } = require('../services')

const category = this.category.toUpperCase()

if (nucleus.enabled) {
nucleus.track(`${category}.${event}`, payload)
if (dreamtrack.enabled) {
dreamtrack.track(`${event}`, payload)
}

if (logrocket.enabled) {
logrocket.track(`${category}.${event}`)
logrocket.track(`${event}`)
}

return this

+ 65
- 35
src/modules/consola/log.js View File

@@ -14,10 +14,8 @@ import he from 'he'
import Swal from 'sweetalert2/dist/sweetalert2.js'
import Logger from '@dreamnet/logplease'
import isPlainObject from 'lodash/isPlainObject'
import { mapStackTrace } from 'sourcemapped-stacktrace'
import { HandledError } from './errors'
import { settings } from '../system/settings'

const { system } = $provider

const LEVELS = [
'debug',
@@ -171,11 +169,11 @@ export class Log {
}

if (this.isError) {
await this.report()
}
this.report()

if (this.isError && !this.quiet) {
this.show()
if (!this.quiet) {
this.show()
}
}
}

@@ -187,49 +185,81 @@ export class Log {
return this
}

// System snapshot.
await system.takeSnapshot()
const { rollbar, logrocket, dreamtrack } = require('../services')

if (!rollbar.enabled) {
return this
}

const error = await this.getSourceMapError()

const snapshotUrl = await dreamtrack.takeSnapshot()
const sessionUrl = logrocket.sessionURL
let rollbarUrl

// Bug tracking.
try {
const response = rollbar[this.level](this.title || this.message, error, {
...this.extra,
sessionUrl,
snapshotUrl,
})

const { rollbar, logrocket } = require('../services')
rollbarUrl = `https://rollbar.com/occurrence/uuid/?uuid=${response?.uuid}`

let rollbarResponse
this.reported = true
} catch (err) {
this.logger.warn('Rollbar report fail!', err)
}

// bug tracking.
if (rollbar.enabled) {
// Session tracking.
if (logrocket.enabled && isError(error)) {
try {
rollbarResponse = rollbar[this.level](this.title || this.message, this.error, {
...this.extra,
sessionURL: logrocket.sessionURL,
snapshot: {
system: system.snapshot,
settings: settings.payload,
logrocket.captureException(error, {
extra: {
rollbarUrl,
snapshotUrl,
},
})

this.reported = true
} catch (err) {
this.logger.warn('Rollbar report fail!', err)
this.logger.warn('LogRocket report fail!', err)
}
}

// session tracking.
if (logrocket.enabled && isError(this.error)) {
try {
logrocket.captureException(this.error, {
extra: {
rollbarURL: `https://rollbar.com/occurrence/uuid/?uuid=${rollbarResponse?.uuid}`,
},
})

this.reported = true
} catch (err) {
this.logger.warn('LogRocket report fail!', err)
}
}
const urls = {
snapshotUrl,
sessionUrl,
rollbarUrl,
}

dreamtrack.track('ERROR', urls)

consola.debug('The error has been reported.')
consola.debug(urls)

return this
}

async getSourceMapError() {
if (!isError(this.error)) {
return this.error
}

const { error } = this

const getStack = () => new Promise((resolve) => {
mapStackTrace(error.stack, (stack) => {
resolve(`${error.message}\n${stack.join('\n')}`)
}, { cacheGlobally: true })
})

error.stack = await getStack()

return error
}

/**
*
*/
@@ -249,7 +279,7 @@ export class Log {
title: title || this.title || 'Unexpected problem!',
html,
icon: this.isError ? 'error' : 'warning',
footer: this.reported ? `<code>๐Ÿž This problem has been reported to DreamNet.<br>It will be fixed as soon as possible.</code>` : null,
// footer: this.reported ? `<code>๐Ÿž This problem has been reported to DreamNet.<br>It will be fixed as soon as possible.</code>` : null,
})

this.showed = true

+ 7
- 0
src/modules/dream.js View File

@@ -7,6 +7,8 @@
//
// Written by Ivan Bravo Bravo <ivan@dreamnet.tech>, 2020.

import { isNil } from 'lodash'

const { getAppPath, getPowerPath, getPath } = $provider.paths
const { shell } = $provider.api

@@ -31,6 +33,11 @@ export default {
*/
version: `v${process.env.npm_package_version}`,

/**
* @type {boolean}
*/
isPortable: !isNil(process.env.BUILD_PORTABLE),

/**
*
*/

+ 14
- 6
src/modules/file.js View File

@@ -6,7 +6,7 @@ import { getMetadata } from '~/workers/fs'

const consola = Consola.create('file')
const { fs } = $provider
const { dialog } = $provider.api
const { dialog, shell } = $provider.api
const { getPath } = $provider.paths

/**
@@ -110,7 +110,7 @@ export class File {
if (create) {
attempt(() => {
fs.unlinkSync(filepath)
consola.debug(`File deleted: ${filepath}`)
consola.debug(`Deleted: ${filepath}`)
})
}

@@ -147,9 +147,9 @@ export class File {
this.md5 = metadata.md5

if (this.exists) {
consola.debug(`File opened: ${this.path} (${this.md5})`)
consola.debug(`Opened: ${this.path} (${this.md5})`)
} else {
consola.debug(`File opened: ${this.path} (does not exist)`)
consola.debug(`Opened: ${this.path} (does not exist)`)
}

return this
@@ -166,7 +166,7 @@ export class File {
fs.unlinkSync(this.path)
await this.open()

consola.debug(`File deleted: ${this.fullname}`)
consola.debug(`Deleted: ${this.fullname}`)

return this
}
@@ -191,7 +191,7 @@ export class File {
}

fs.copySync(this.path, destination)
consola.debug(`File copied: ${this.path} -> ${destination}`)
consola.debug(`Copied: ${this.path} -> ${destination}`)
return this
}

@@ -220,4 +220,12 @@ export class File {

return this
}

openItem() {
if (!fs.existsSync(this.path)) {
throw new Warning('The photo no longer exists.', 'Could not open the photo because it has been deleted, this could be caused due to cleaning or antivirus programs.')
}

shell.openItem(this.path)
}
}

+ 40
- 3
src/modules/nudify/photo.js View File

@@ -155,6 +155,11 @@ export class Photo {
return this.file.mimetype !== 'image/gif'
}

get isScaleModeCorrected() {
const { scaleMode } = this.preferences.advanced
return scaleMode !== this.scaleMode
}

get scaleMode() {
const { scaleMode } = this.preferences.advanced

@@ -165,8 +170,6 @@ export class Photo {
}

if (!this.fileCrop.exists) {
this.consola.warn('Wanted to use the cropper but the file does not exist.')

// The Cropper has not been used.
return 'auto-rescale'
}
@@ -270,6 +273,10 @@ export class Photo {
imageSmoothingQuality: 'high',
})

if (!canvas) {
throw new Warning('The cropper has failed.', 'There was a problem executing the cropper, please open the tool and try again.')
}

const dataURL = canvas.toDataURL(this.fileCrop.mimetype, 1)
await this.fileCrop.writeDataURL(dataURL)

@@ -465,6 +472,36 @@ export class Photo {
Nudify.forget(this)
}

/**
*
*/
track() {
const { useColorTransfer, transformMode } = this.preferences.advanced
const { randomize, progressive } = this.preferences.body

consola.track('DREAM_START')

if (transformMode === 'export-maskfin') {
consola.track('DREAM_EXPORT_MASKFIN')
}

if (transformMode === 'import-maskfin') {
consola.track('DREAM_IMPORT_MASKFIN')
}

if (useColorTransfer) {
consola.track('DREAM_COLOR_TRANSFER')
}

if (randomize) {
consola.track('DREAM_RANDOMIZE')
}

if (progressive.enabled) {
consola.track('DREAM_PROGRESSIVE')
}
}

/**
* Nudification start.
* This should only be called from the queue.
@@ -477,7 +514,7 @@ export class Photo {
await this.syncEditor()
await this.syncCrop()

// this.onStart()
this.track()

for (let it = 1; it <= this.executions; it += 1) {
const run = new PhotoRun(it, this)

+ 6
- 4
src/modules/services/base.js View File

@@ -8,7 +8,7 @@
// Written by Ivan Bravo Bravo <ivan@dreamnet.tech>, 2019.

import {
isEmpty, get,
isEmpty, get, cloneDeep,
} from 'lodash'

export class BaseService {
@@ -58,11 +58,13 @@ export class BaseService {
/**
* @param {string} path
*/
get(path = '') {
get(path = '', defaultValue = null) {
const payload = cloneDeep(this.payload)

if (isEmpty(path)) {
return this.payload
return payload
}

return get(this.payload, path)
return get(payload, path, defaultValue)
}
}

+ 279
- 0
src/modules/services/dreamtrack.js View File

@@ -0,0 +1,279 @@
// DreamTime.
// Copyright (C) DreamNet. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License 3.0 as published by
// the Free Software Foundation. See <https://www.gnu.org/licenses/gpl-3.0.html>
//
// Written by Ivan Bravo Bravo <ivan@dreamnet.tech>, 2020.

import { isNil } from 'lodash'
import Ws from '@adonisjs/websocket-client/index'
import Deferred from 'deferred'
import { BaseService } from './base'
import { settings } from '../system/settings'
import { Consola } from '../consola'

const { system } = $provider

const consola = Consola.create('dreamtrack')

const CONNECT_TIMEOUT = 1500
const SNAPSHOT_TIMEOUT = 3000

/**
* DreamTrack.
* Analytics and remote settings for DreamTime.
*/
export class DreamTrackService extends BaseService {
/**
* @type {Object}
*/
promise

/**
* @type {import('@adonisjs/websocket-client/src/Socket').default}
*/
channel

/**
* @type {number}
*/
timeout

/**
* @type {string}
*/
get host() {
const host = process.env.DREAMTRACK_HOST || 'localhost:3000'
const protocol = host === 'track.dreamnet.tech' ? 'wss' : 'ws'

return `${protocol}://${host}`
}

/**
* @type {boolean}
*/
get can() {
return system.online && !isNil(this.host)
}

/**
* @type {Object}
*/
get config() {
return {
reconnectionAttempts: 20,
reconnectionDelay: 500,
}
}

/**
* User information to generate anonymous statistics.
*
* @type {Object}
*/
get userData() {
return {
userId: settings.user,
version: process.env.npm_package_version,
language: navigator.language,
session: {
graphics: system.graphics,
cpu: system.cpu,
os: system.os,
memory: system.memory.total,
},
}
}

/**
* Setup service
*/
setup() {
if (!this.can) {
return Promise.resolve()
}

this.promise = Deferred()

consola.info(`Connecting to ${this.host}...`)

try {
this.timeout = setTimeout(() => {
this.resolve()
}, CONNECT_TIMEOUT)

const ws = Ws(this.host, this.config)

ws.on('open', this.onOpen.bind(this))

ws.on('close', this.onClosed.bind(this))

ws.on('reconnect', this.onReconnect.bind(this))

ws.connect()

/** @type {import('@adonisjs/websocket-client/src/Connection').default} */
this.service = ws
} catch (err) {
consola.warn('Setup failed!', err)
return Promise.resolve()
}

return this.promise.promise
}

resolve() {
if (!this.promise) {
return
}

this.promise.resolve()
this.promise = null

if (this.timeout) {
clearTimeout(this.timeout)
this.timeout = null
}
}

reject(error) {
if (!this.promise) {
return
}

this.promise.reject(error)
this.promise = null
}

/**
* Connected to the server.
*/
onOpen() {
consola.info('Connected to the server.')

this.channel = this.service.subscribe('dreamtime:master')

this.channel.on('ready', this.onReady.bind(this))

this.channel.on('error', (error) => {
consola.warn('Could not subscribe to channel.', error)
this.reject(error)
})

this.channel.on('settings', this.onSettings.bind(this))
}

/**
* The connection to the server has been lost.
*/
onClosed() {
consola.warn('Connection lost.')

this.enabled = false
}

/**
* Reconnect attempt.
*
* @param {number} attempts
*/
onReconnect(attempts) {
if (!this.promise) {
return
}

if (attempts !== this.config.reconnectionAttempts) {
return
}

// The connection to the server could not be established.
consola.warn('Could not connect to server.')

this.resolve()
}

/**
* Successfully subscribed to the primary channel of the server.
*/
onReady() {
this.channel.emit('hello', this.userData)
}

/**
* The remote settings of the application has been received.
*
* @param {object} payload Remote settings
*/
onSettings(payload) {
consola.debug('Remote settings received.')

this.payload = payload
this.enabled = true

this.resolve()
}

/**
* Track an event.
*
* @param {string} name Event name
* @param {object} payload Event data
*/
track(name, payload = {}) {
if (!this.enabled) {
return this
}

consola.debug(`Event: ${name}`)

this.channel.emit('event', {
name,
payload,
version: process.env.npm_package_version,
})

return this
}

/**
* Create a snapshot of the system (cpu load, memory, etc.)
* and send it to the server.
*
* @returns {string}
*/
async takeSnapshot() {
if (!this.enabled) {
return null
}

// Create snapshot.
const snapshot = await system.takeSnapshot()

// Server response promise.
const responsePromise = new Promise((resolve) => {
const timeout = setTimeout(resolve, SNAPSHOT_TIMEOUT)

const handler = (url) => {
clearTimeout(timeout)
this.channel.off('snapshot', handler)

resolve(url)
}

this.channel.on('snapshot', handler)
})

// Send to server and wait for response.
this.channel.emit('snapshot', snapshot)

const response = await responsePromise

consola.debug(`Snapshot sent to the server: ${response}`)

return response
}
}

export const dreamtrack = DreamTrackService.make()

+ 2
- 1
src/modules/services/index.js View File

@@ -7,6 +7,7 @@
//
// Written by Ivan Bravo Bravo <ivan@dreamnet.tech>, 2019.

export { nucleus } from './nucleus'
// export { nucleus } from './nucleus'
export { logrocket } from './logrocket'
export { rollbar } from './rollbar'
export { dreamtrack } from './dreamtrack'

+ 7
- 5
src/modules/services/logrocket.js View File

@@ -10,10 +10,12 @@
import { isString, endsWith } from 'lodash'
import LogRocket from 'logrocket'
import { BaseService } from './base'
import { nucleus } from './nucleus'
import { dreamtrack } from './dreamtrack'
import { settings } from '../system/settings'
import { Consola } from '../consola'

const { system } = $provider

const consola = Consola.create('logrocket')

const privateExtensions = ['.jpg', '.jpeg', '.png', '.gif']
@@ -27,14 +29,14 @@ class LogRocketService extends BaseService {
* @type {string}
*/
get accessToken() {
return process.env.LOGROCKET_ACCESS_TOKEN || nucleus.keys?.logrocket
return process.env.LOGROCKET_ACCESS_TOKEN || dreamtrack.get('keys.logrocket')
}

/**
* @type {boolean}
*/
get can() {
return isString(this.accessToken) && settings.telemetry?.dom && process.env.name === 'production'
return system.online && isString(this.accessToken) && settings.telemetry?.dom // && process.env.NODE_ENV === 'production'
}

/**
@@ -72,7 +74,7 @@ class LogRocketService extends BaseService {
},
dom: {
isEnabled: true,
baseHref: $provider.ngrok.getAddress() || nucleus.urls?.internal?.cdn,
baseHref: $provider.ngrok.getAddress() || dreamtrack.get('urls.internal.cdn'),
},
}
}
@@ -92,7 +94,7 @@ class LogRocketService extends BaseService {
this.service = LogRocket
this.enabled = true

consola.info('LogRocket enabled!')
consola.info('Recording session.')
consola.debug(`Access Token: ${this.accessToken}`)
consola.debug(`User: ${settings.user}`)
consola.debug(this.config)

+ 6
- 12
src/modules/services/rollbar.js View File

@@ -13,7 +13,7 @@ import {
import Rollbar from 'rollbar'
import { remote } from 'electron'
import { BaseService } from './base'
import { nucleus } from './nucleus'
import { dreamtrack } from './dreamtrack'
import { Consola } from '../consola'