├── README.md
├── .gitignore
├── frontend
├── .browserslistrc
├── babel.config.js
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── assets
│ │ └── logo.png
│ ├── views
│ │ ├── AboutView.vue
│ │ └── MainView.vue
│ ├── shims-vue.d.ts
│ ├── App.vue
│ ├── router
│ │ └── index.ts
│ ├── main.ts
│ └── components
│ │ └── MainComponent.vue
├── vue.config.js
├── .gitignore
├── README.md
├── .eslintrc.js
├── tsconfig.json
└── package.json
├── staticAssets
└── handler.go
├── go.mod
├── LICENSE
├── backend
├── handlers_test.go
└── handlers.go
├── mainWindow
└── run.go
├── main.go
└── go.sum
/README.md:
--------------------------------------------------------------------------------
1 | # desktop-web-app
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | /desktop-web-app
3 | /desktop-web-app.exe
--------------------------------------------------------------------------------
/frontend/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 | not ie 11
5 |
--------------------------------------------------------------------------------
/frontend/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ["@vue/cli-plugin-babel/preset"],
3 | };
4 |
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apis/desktop-web-app/master/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apis/desktop-web-app/master/frontend/src/assets/logo.png
--------------------------------------------------------------------------------
/frontend/src/views/AboutView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
This is an about page
4 |
5 |
6 |
--------------------------------------------------------------------------------
/frontend/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | declare module '*.vue' {
3 | import type { DefineComponent } from 'vue'
4 | const component: DefineComponent<{}, {}, any>
5 | export default component
6 | }
7 |
--------------------------------------------------------------------------------
/frontend/vue.config.js:
--------------------------------------------------------------------------------
1 | const { defineConfig } = require("@vue/cli-service");
2 | module.exports = defineConfig({
3 | transpileDependencies: true,
4 | publicPath: process.env.NODE_ENV === "production" ? "/ui" : "/",
5 | });
6 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/frontend/src/views/MainView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
18 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | # frontend
2 |
3 | ## Project setup
4 | ```
5 | npm install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | npm run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | npm run build
16 | ```
17 |
18 | ### Lints and fixes files
19 | ```
20 | npm run lint
21 | ```
22 |
23 | ### Customize configuration
24 | See [Configuration Reference](https://cli.vuejs.org/config/).
25 |
--------------------------------------------------------------------------------
/frontend/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
31 |
--------------------------------------------------------------------------------
/frontend/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true,
5 | },
6 | extends: [
7 | "plugin:vue/vue3-essential",
8 | "eslint:recommended",
9 | "@vue/typescript/recommended",
10 | "plugin:prettier/recommended",
11 | ],
12 | parserOptions: {
13 | ecmaVersion: 2020,
14 | },
15 | rules: {
16 | "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
17 | "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
18 | "prettier/prettier": [
19 | "error",
20 | {
21 | "endOfLine": "auto"
22 | },
23 | ],
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/frontend/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
2 | import MainView from "../views/MainView.vue";
3 |
4 | const routes: Array = [
5 | {
6 | path: "/",
7 | name: "home",
8 | component: MainView,
9 | },
10 | {
11 | path: "/about",
12 | name: "about",
13 | // route level code-splitting
14 | // this generates a separate chunk (about.[hash].js) for this route
15 | // which is lazy-loaded when the route is visited.
16 | component: () =>
17 | import(/* webpackChunkName: "about" */ "../views/AboutView.vue"),
18 | },
19 | ];
20 |
21 | const router = createRouter({
22 | history: createWebHashHistory(),
23 | routes,
24 | });
25 |
26 | export default router;
27 |
--------------------------------------------------------------------------------
/staticAssets/handler.go:
--------------------------------------------------------------------------------
1 | package staticAssets
2 |
3 | import (
4 | "embed"
5 | "io/fs"
6 | "net/http"
7 | "os"
8 | "path"
9 | )
10 |
11 | type pseudoFs func(name string) (fs.File, error)
12 |
13 | func (f pseudoFs) Open(name string) (fs.File, error) {
14 | return f(name)
15 | }
16 |
17 | func Handler(embedFs embed.FS, embedFsRoot string, urlPrefix string, defaultUrl string) http.Handler {
18 | handler := pseudoFs(func(name string) (fs.File, error) {
19 | assetPath := path.Join(embedFsRoot, name)
20 |
21 | file, err := embedFs.Open(assetPath)
22 | if os.IsNotExist(err) {
23 | return embedFs.Open(path.Join(embedFsRoot, defaultUrl))
24 | }
25 |
26 | return file, err
27 | })
28 |
29 | return http.StripPrefix(urlPrefix, http.FileServer(http.FS(handler)))
30 | }
31 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module desktop-web-app
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/chromedp/chromedp v0.9.1
7 | github.com/go-chi/chi/v5 v5.0.8
8 | github.com/sirupsen/logrus v1.9.0
9 | github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816
10 | nhooyr.io/websocket v1.8.7
11 | )
12 |
13 | require (
14 | github.com/chromedp/cdproto v0.0.0-20230220211738-2b1ec77315c9 // indirect
15 | github.com/chromedp/sysutil v1.0.0 // indirect
16 | github.com/gobwas/httphead v0.1.0 // indirect
17 | github.com/gobwas/pool v0.2.1 // indirect
18 | github.com/gobwas/ws v1.1.0 // indirect
19 | github.com/josharian/intern v1.0.0 // indirect
20 | github.com/klauspost/compress v1.10.3 // indirect
21 | github.com/mailru/easyjson v0.7.7 // indirect
22 | github.com/stretchr/testify v1.8.1 // indirect
23 | golang.org/x/sys v0.6.0 // indirect
24 | )
25 |
--------------------------------------------------------------------------------
/frontend/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from "vue";
2 | import App from "./App.vue";
3 | import router from "./router";
4 |
5 | const app = createApp(App);
6 |
7 | const url = new URL("/api/time-event", window.location.href);
8 | url.protocol = url.protocol.replace("http", "ws");
9 | const webSocket = new WebSocket(url.href, ["time"]);
10 |
11 | // // Disable common Chrome shortcuts like Ctrl+N, Ctrl+T, Ctrl+F etc.
12 | // window.addEventListener("keydown", (event: KeyboardEvent) => {
13 | // if (event.ctrlKey) {
14 | // event.stopPropagation();
15 | // event.preventDefault();
16 | // }
17 | // // return true;
18 | // });
19 | //
20 | // // Disable Chrome context menu
21 | // window.addEventListener("contextmenu", (event: MouseEvent) => {
22 | // event.stopPropagation();
23 | // event.preventDefault();
24 | // });
25 |
26 | app.provide("webSocket", webSocket);
27 | app.use(router).mount("#app");
28 |
--------------------------------------------------------------------------------
/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "strict": true,
6 | "jsx": "preserve",
7 | "moduleResolution": "node",
8 | "skipLibCheck": true,
9 | "esModuleInterop": true,
10 | "allowSyntheticDefaultImports": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "useDefineForClassFields": true,
13 | "sourceMap": true,
14 | "baseUrl": ".",
15 | "types": [
16 | "webpack-env"
17 | ],
18 | "paths": {
19 | "@/*": [
20 | "src/*"
21 | ]
22 | },
23 | "lib": [
24 | "esnext",
25 | "dom",
26 | "dom.iterable",
27 | "scripthost"
28 | ]
29 | },
30 | "include": [
31 | "src/**/*.ts",
32 | "src/**/*.tsx",
33 | "src/**/*.vue",
34 | "tests/**/*.ts",
35 | "tests/**/*.tsx"
36 | ],
37 | "exclude": [
38 | "node_modules"
39 | ]
40 | }
41 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "core-js": "^3.30.0",
12 | "vue": "^3.2.47",
13 | "vue-router": "^4.1.6"
14 | },
15 | "devDependencies": {
16 | "@typescript-eslint/eslint-plugin": "^5.57.1",
17 | "@typescript-eslint/parser": "^5.57.1",
18 | "@vue/cli-plugin-babel": "~5.0.8",
19 | "@vue/cli-plugin-eslint": "~5.0.8",
20 | "@vue/cli-plugin-router": "~5.0.8",
21 | "@vue/cli-plugin-typescript": "~5.0.8",
22 | "@vue/cli-service": "~5.0.8",
23 | "@vue/eslint-config-typescript": "^11.0.2",
24 | "eslint": "^8.37.0",
25 | "eslint-config-prettier": "^8.8.0",
26 | "eslint-plugin-prettier": "^4.2.1",
27 | "eslint-plugin-vue": "^9.10.0",
28 | "prettier": "^2.8.7",
29 | "typescript": "~5.0.3"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Alexey Pisanko
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/backend/handlers_test.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/sirupsen/logrus"
6 | easy "github.com/t-tomalak/logrus-easy-formatter"
7 | "net/http"
8 | "net/http/httptest"
9 | "os"
10 | "testing"
11 | "time"
12 | )
13 |
14 | func init() {
15 | log = &logrus.Logger{
16 | Out: os.Stderr,
17 | Level: logrus.DebugLevel,
18 | Formatter: &easy.Formatter{
19 | TimestampFormat: "2006-01-02 15:04:05",
20 | LogFormat: "[%lvl%] %time% - %msg%",
21 | },
22 | }
23 | }
24 |
25 | func TestHandleGetTime(t *testing.T) {
26 |
27 | // Create a ResponseRecorder to record the response
28 | responseRecorder := httptest.NewRecorder()
29 |
30 | // Create a request to pass to the handler
31 | request, err := http.NewRequest("GET", "/get-time", nil)
32 | if err != nil {
33 | t.Fatal(err)
34 | }
35 |
36 | initialTime := time.Now().Format(time.RFC3339)
37 |
38 | // Call the handler
39 | handleGetTime()(responseRecorder, request)
40 |
41 | // Check the status code is what we expect
42 | if status := responseRecorder.Code; status != http.StatusOK {
43 | t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
44 | }
45 |
46 | var returnedTime string
47 | err = json.Unmarshal(responseRecorder.Body.Bytes(), &returnedTime)
48 | if err != nil {
49 | t.Errorf("unmarshal failed, for %v", responseRecorder.Body.String())
50 | }
51 |
52 | if initialTime != returnedTime {
53 | t.Errorf("time is not matching, for %v and %v", initialTime, returnedTime)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/frontend/src/components/MainComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg }}
4 |
5 |
6 |
7 |
8 |
{{ dateTime }}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
71 |
72 |
99 |
--------------------------------------------------------------------------------
/mainWindow/run.go:
--------------------------------------------------------------------------------
1 | package mainWindow
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/chromedp/chromedp"
7 | "github.com/sirupsen/logrus"
8 | )
9 |
10 | const windowLeft = 0
11 | const windowTop = 0
12 | const windowWidth = 800
13 | const windowHeight = 600
14 |
15 | const jsCode = `
16 | // Disable common Chrome shortcuts like Ctrl+N, Ctrl+T, Ctrl+F etc.
17 | window.addEventListener("keydown", (event) => {
18 | if (event.ctrlKey) {
19 | event.stopPropagation();
20 | event.preventDefault();
21 | }
22 | });
23 |
24 | // Disable Chrome context menu
25 | window.addEventListener("contextmenu", (event) => {
26 | event.stopPropagation();
27 | event.preventDefault();
28 | });
29 | `
30 |
31 | func Run(host string, port int, url string, log *logrus.Logger) {
32 | log.Infof("Starting main window\n")
33 |
34 | opts := []chromedp.ExecAllocatorOption{
35 | chromedp.NoFirstRun,
36 | chromedp.NoDefaultBrowserCheck,
37 | //chromedp.Flag("app", true),
38 | //chromedp.Flag("app", "data:text/html,"),
39 | //chromedp.Flag("app", "data:text/html,TEST"),
40 | //chromedp.Flag("app", "data:text/html,App"),
41 | chromedp.Flag("app", "data:text/html,App"),
42 | //chromedp.Flag("app", "data:text/html,"),
43 | // chromedp.Flag("window-size", fmt.Sprintf("%d,%d", windowWidth, windowHeight)),
44 | // chromedp.Flag("window-position", fmt.Sprintf("%d,%d", windowLeft, windowTop)),
45 | chromedp.Flag("start-maximized", true),
46 | }
47 |
48 | allocatorContext, _ := chromedp.NewExecAllocator(context.Background(), opts...)
49 |
50 | ctx, cancel := chromedp.NewContext(
51 | allocatorContext,
52 | //chromedp.WithDebugf(log.Printf),
53 | )
54 | defer cancel()
55 |
56 | if err := chromedp.Run(ctx, chromedp.Tasks{
57 | chromedp.Navigate(fmt.Sprintf("http://%s:%d%s", host, port, url)),
58 | chromedp.Evaluate(jsCode, nil),
59 | }); err != nil {
60 | log.Fatalf("chromedp.Run() failed: %s\n",
61 | fmt.Errorf("%w", err))
62 | }
63 |
64 | <-chromedp.FromContext(ctx).Browser.LostConnection
65 |
66 | log.Infof("Stopping main window\n")
67 | }
68 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "desktop-web-app/backend"
6 | "desktop-web-app/mainWindow"
7 | "desktop-web-app/staticAssets"
8 | "embed"
9 | "fmt"
10 | "github.com/go-chi/chi/v5"
11 | "github.com/go-chi/chi/v5/middleware"
12 | "github.com/sirupsen/logrus"
13 | easy "github.com/t-tomalak/logrus-easy-formatter"
14 | "net"
15 | "net/http"
16 | "os"
17 | "time"
18 | )
19 |
20 | const localHost = "127.0.0.1"
21 | const anyPort = 0
22 | const uiUrlPrefix = "/ui"
23 | const apiUrlPrefix = "/api"
24 | const embedFsRoot = "frontend/dist"
25 | const defaultUiUrl = "index.html"
26 |
27 | //go:embed frontend/dist
28 | var embedFs embed.FS
29 |
30 | var log *logrus.Logger
31 |
32 | func main() {
33 | log = &logrus.Logger{
34 | Out: os.Stderr,
35 | Level: logrus.DebugLevel,
36 | Formatter: &easy.Formatter{
37 | TimestampFormat: "2006-01-02 15:04:05",
38 | LogFormat: "[%lvl%] %time% - %msg%",
39 | },
40 | }
41 |
42 | log.Infof("Application starting\n")
43 |
44 | listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", localHost, anyPort))
45 | if err != nil {
46 | log.Fatalf("Server net.Listen() failed: %s\n",
47 | fmt.Errorf("%w", err))
48 | }
49 |
50 | ephemeralPort := listener.Addr().(*net.TCPAddr).Port
51 |
52 | log.Infof("Ephemeral port: %d\n", ephemeralPort)
53 |
54 | router := chi.NewRouter()
55 | router.Use(middleware.Logger)
56 | router.Mount(apiUrlPrefix, backend.Router(log))
57 | router.Handle(uiUrlPrefix+"*", staticAssets.Handler(embedFs, embedFsRoot, uiUrlPrefix, defaultUiUrl))
58 |
59 | server := &http.Server{
60 | Handler: router,
61 | }
62 |
63 | go func() {
64 | log.Infof("Server starting\n")
65 |
66 | err := server.Serve(listener)
67 | if err != nil {
68 | if err != nil && err != http.ErrServerClosed {
69 | log.Fatalf("Server ListenAndServe() failed: %s\n",
70 | fmt.Errorf("%w", err))
71 | }
72 | }
73 |
74 | log.Infof("Server stopped\n")
75 | }()
76 |
77 | mainWindow.Run(localHost, ephemeralPort, uiUrlPrefix, log)
78 |
79 | log.Infof("Server stopping\n")
80 |
81 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
82 | defer func() {
83 | cancel()
84 | }()
85 |
86 | if err := server.Shutdown(ctx); err != nil {
87 | log.Fatalf("Server Shutdown() failed: %s\n",
88 | fmt.Errorf("%w", err))
89 | }
90 |
91 | log.Infof("Application stopped\n")
92 | }
93 |
--------------------------------------------------------------------------------
/backend/handlers.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/go-chi/chi/v5"
7 | "github.com/sirupsen/logrus"
8 | "net/http"
9 | "nhooyr.io/websocket"
10 | "nhooyr.io/websocket/wsjson"
11 | "time"
12 | )
13 |
14 | var log *logrus.Logger
15 |
16 | func Router(externalLog *logrus.Logger) chi.Router {
17 | log = externalLog
18 | router := chi.NewRouter()
19 | router.Get("/get-time", handleGetTime())
20 | router.Get("/time-event", handleTimeEvent())
21 | return router
22 | }
23 |
24 | func handleGetTime() http.HandlerFunc {
25 | return func(writer http.ResponseWriter, request *http.Request) {
26 | log.Infof("GetTime request from client\n")
27 |
28 | writer.Header().Set("Content-Type", "application/json")
29 |
30 | message, err := json.Marshal(getCurrentTime())
31 | if err != nil {
32 | log.Errorf("json.Marshal() failed: %s\n",
33 | fmt.Errorf("%w", err))
34 | writer.WriteHeader(http.StatusInternalServerError)
35 | return
36 | }
37 |
38 | _, err = writer.Write(message)
39 | if err != nil {
40 | log.Errorf("writer.Write() failed: %s\n",
41 | fmt.Errorf("%w", err))
42 | writer.WriteHeader(http.StatusInternalServerError)
43 | return
44 | }
45 | }
46 | }
47 |
48 | func getCurrentTime() string {
49 | return time.Now().Format(time.RFC3339)
50 | }
51 |
52 | func handleTimeEvent() http.HandlerFunc {
53 | return func(writer http.ResponseWriter, request *http.Request) {
54 | log.Infof("TimeEvent request from client\n")
55 |
56 | conn, err := websocket.Accept(writer, request, &websocket.AcceptOptions{
57 | Subprotocols: []string{"time"},
58 | })
59 | if err != nil {
60 | log.Errorf("websocket.Accept() failed: %s\n",
61 | fmt.Errorf("%w", err))
62 | return
63 | }
64 |
65 | defer func() {
66 | err := conn.Close(websocket.StatusInternalError, "unexpected error")
67 | if err != nil {
68 | log.Errorf("conn.Close() failed: %s\n",
69 | fmt.Errorf("%w", err))
70 | return
71 | }
72 |
73 | log.Infof("websocket connection closed\n")
74 | }()
75 |
76 | for {
77 | //if websocket.CloseStatus(err) != -1 {
78 | // log.Infof("websocket closed\n")
79 | // return
80 | //}
81 |
82 | currentTime := getCurrentTime()
83 |
84 | log.Infof("about to send event message: %s\n", currentTime)
85 |
86 | err = wsjson.Write(request.Context(), conn, currentTime)
87 | if err != nil {
88 | log.Errorf("wsjson.Write() failed: %s\n",
89 | fmt.Errorf("%w", err))
90 | return
91 | }
92 |
93 | log.Infof("sent event message: %s\n", currentTime)
94 |
95 | time.Sleep(3 * time.Second)
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/chromedp/cdproto v0.0.0-20230220211738-2b1ec77315c9 h1:wMSvdj3BswqfQOXp2R1bJOAE7xIQLt2dlMQDMf836VY=
2 | github.com/chromedp/cdproto v0.0.0-20230220211738-2b1ec77315c9/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
3 | github.com/chromedp/chromedp v0.9.1 h1:CC7cC5p1BeLiiS2gfNNPwp3OaUxtRMBjfiw3E3k6dFA=
4 | github.com/chromedp/chromedp v0.9.1/go.mod h1:DUgZWRvYoEfgi66CgZ/9Yv+psgi+Sksy5DTScENWjaQ=
5 | github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
6 | github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
11 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
12 | github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
13 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
14 | github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
15 | github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
16 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
17 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
18 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
19 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
20 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
21 | github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
22 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
23 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
24 | github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
25 | github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
26 | github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
27 | github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
28 | github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
29 | github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
30 | github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA=
31 | github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
32 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
33 | github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
34 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
35 | github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
36 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
37 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
38 | github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
39 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
40 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
41 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
42 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
43 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
44 | github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
45 | github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
46 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
47 | github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
48 | github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
49 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
50 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
51 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
52 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
53 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
54 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
55 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
56 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
57 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
58 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
59 | github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
60 | github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
61 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
62 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
63 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
64 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
65 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
66 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
67 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
68 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
69 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
70 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
71 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
72 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
73 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
74 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
75 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
76 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
77 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
78 | github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 h1:J6v8awz+me+xeb/cUTotKgceAYouhIB3pjzgRd6IlGk=
79 | github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816/go.mod h1:tzym/CEb5jnFI+Q0k4Qq3+LvRF4gO3E2pxS8fHP8jcA=
80 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
81 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
82 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
83 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
84 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
85 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
86 | golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
87 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
88 | golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
89 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
90 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
91 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
92 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
93 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
94 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
95 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
96 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
97 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
98 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
99 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
100 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
101 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
102 | nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
103 | nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
104 |
--------------------------------------------------------------------------------