Skip to content

Commit a163b16

Browse files
authored
feat: Browser API support (#38)
* chore: add .worktrees to .gitignore for git worktrees support * feat: add auto-discovery router with Gin integration * feat: integrate auto-discovery router into HTTP server * feat: enable dual-mode with HTTP server on port 8081 * feat: add TypeScript client generator tool * feat: generate TypeScript clients for all services * feat: migrate JwtDebugger to use generated API clients * test: add integration tests for HTTP API * docs: add browser mode documentation * fix: handle primitive and multi-parameter methods in router * feat: browser api support * feat: add unit tests for JWT and barcode services, and integration tests for the API router.
1 parent 6ba3dd2 commit a163b16

44 files changed

Lines changed: 3206 additions & 491 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ runtime-debug.js
99
runtime.js
1010
.task/*
1111
/package.json
12+
.worktrees/

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ Essential software development tools for everyday tasks.
1111

1212
## Features
1313

14+
### **Browser Support**
15+
16+
DevToolbox now works in both desktop and browser modes:
17+
18+
- **Desktop**: Native Wails application with native performance (default)
19+
- **Browser**: Access via `http://localhost:8081` when the desktop app is running
20+
21+
The frontend automatically detects the environment and uses the appropriate API (Wails runtime for desktop, HTTP for browser). See [docs/BROWSER_MODE.md](docs/BROWSER_MODE.md) for details.
22+
1423
### **Text Based Converter** (Unified Tool)
1524
The central hub with 45+ algorithms across 5 categories:
1625

cmd/genservices/generator.go

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package main
2+
3+
import (
4+
_ "embed"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
"text/template"
10+
)
11+
12+
//go:embed templates/typescript.tmpl
13+
var typescriptTemplate string
14+
15+
// Generator generates TypeScript code
16+
type Generator struct {
17+
outputDir string
18+
tmpl *template.Template
19+
}
20+
21+
// NewGenerator creates a new generator
22+
func NewGenerator(outputDir string) (*Generator, error) {
23+
tmpl, err := template.New("typescript").Parse(typescriptTemplate)
24+
if err != nil {
25+
return nil, err
26+
}
27+
28+
return &Generator{
29+
outputDir: outputDir,
30+
tmpl: tmpl,
31+
}, nil
32+
}
33+
34+
// Generate creates TypeScript files for all services
35+
func (g *Generator) Generate(services []Service) error {
36+
// Create output directories
37+
wailsDir := filepath.Join(g.outputDir, "wails")
38+
httpDir := filepath.Join(g.outputDir, "http")
39+
40+
os.MkdirAll(wailsDir, 0755)
41+
os.MkdirAll(httpDir, 0755)
42+
43+
// Generate individual service files
44+
for _, service := range services {
45+
if err := g.generateWailsService(wailsDir, service); err != nil {
46+
return err
47+
}
48+
if err := g.generateHTTPService(httpDir, service); err != nil {
49+
return err
50+
}
51+
}
52+
53+
// Generate index files
54+
if err := g.generateWailsIndex(wailsDir, services); err != nil {
55+
return err
56+
}
57+
if err := g.generateHTTPIndex(httpDir, services); err != nil {
58+
return err
59+
}
60+
61+
// Generate unified facade
62+
return g.generateUnifiedFacade(g.outputDir, services)
63+
}
64+
65+
func (g *Generator) generateWailsService(dir string, service Service) error {
66+
filename := filepath.Join(dir, toCamelCase(service.Name)+".ts")
67+
68+
data := struct {
69+
ServiceName string
70+
Methods []ServiceMethod
71+
}{
72+
ServiceName: service.Name,
73+
Methods: service.Methods,
74+
}
75+
76+
file, err := os.Create(filename)
77+
if err != nil {
78+
return err
79+
}
80+
defer file.Close()
81+
82+
return g.tmpl.ExecuteTemplate(file, "wails", data)
83+
}
84+
85+
func (g *Generator) generateHTTPService(dir string, service Service) error {
86+
filename := filepath.Join(dir, toCamelCase(service.Name)+".ts")
87+
88+
data := struct {
89+
ServiceName string
90+
Methods []ServiceMethod
91+
}{
92+
ServiceName: service.Name,
93+
Methods: service.Methods,
94+
}
95+
96+
file, err := os.Create(filename)
97+
if err != nil {
98+
return err
99+
}
100+
defer file.Close()
101+
102+
return g.tmpl.ExecuteTemplate(file, "http", data)
103+
}
104+
105+
func (g *Generator) generateWailsIndex(dir string, services []Service) error {
106+
filename := filepath.Join(dir, "index.ts")
107+
108+
var exports []string
109+
for _, svc := range services {
110+
exports = append(exports, fmt.Sprintf("export * as %s from './%s';",
111+
toCamelCase(svc.Name), toCamelCase(svc.Name)))
112+
}
113+
114+
content := strings.Join(exports, "\n")
115+
return os.WriteFile(filename, []byte(content), 0644)
116+
}
117+
118+
func (g *Generator) generateHTTPIndex(dir string, services []Service) error {
119+
filename := filepath.Join(dir, "index.ts")
120+
121+
var exports []string
122+
for _, svc := range services {
123+
exports = append(exports, fmt.Sprintf("export * as %s from './%s';",
124+
toCamelCase(svc.Name), toCamelCase(svc.Name)))
125+
}
126+
127+
content := strings.Join(exports, "\n")
128+
return os.WriteFile(filename, []byte(content), 0644)
129+
}
130+
131+
func (g *Generator) generateUnifiedFacade(dir string, services []Service) error {
132+
filename := filepath.Join(dir, "index.ts")
133+
134+
var serviceImports []string
135+
var serviceMappings []string
136+
137+
for _, svc := range services {
138+
camelName := toCamelCase(svc.Name)
139+
serviceImports = append(serviceImports, fmt.Sprintf(
140+
"import { %s as Wails%s } from './wails/%s';\n"+
141+
"import { %s as HTTP%s } from './http/%s';",
142+
svc.Name, svc.Name, camelName,
143+
svc.Name, svc.Name, camelName))
144+
145+
serviceMappings = append(serviceMappings, fmt.Sprintf(
146+
"export const %s = isWails() ? Wails%s : HTTP%s;",
147+
camelName, svc.Name, svc.Name))
148+
}
149+
150+
content := fmt.Sprintf(`// Auto-generated unified service facade
151+
// Detects runtime environment and uses appropriate implementation
152+
153+
const isWails = () => {
154+
return typeof window !== 'undefined' &&
155+
window.runtime &&
156+
window.runtime.EventsOn !== undefined;
157+
};
158+
159+
%s
160+
161+
%s
162+
`, strings.Join(serviceImports, "\n"), strings.Join(serviceMappings, "\n"))
163+
164+
return os.WriteFile(filename, []byte(content), 0644)
165+
}
166+
167+
// toCamelCase converts PascalCase to camelCase
168+
func toCamelCase(s string) string {
169+
if s == "" {
170+
return s
171+
}
172+
return strings.ToLower(s[:1]) + s[1:]
173+
}

cmd/genservices/main.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"log"
7+
"path/filepath"
8+
)
9+
10+
func main() {
11+
var (
12+
serviceDir = flag.String("services", "service", "Directory containing Go service files")
13+
outputDir = flag.String("output", "frontend/src/generated", "Output directory for generated TypeScript")
14+
)
15+
flag.Parse()
16+
17+
// Get absolute paths
18+
absServiceDir, err := filepath.Abs(*serviceDir)
19+
if err != nil {
20+
log.Fatal(err)
21+
}
22+
23+
absOutputDir, err := filepath.Abs(*outputDir)
24+
if err != nil {
25+
log.Fatal(err)
26+
}
27+
28+
fmt.Printf("Parsing services from: %s\n", absServiceDir)
29+
fmt.Printf("Generating TypeScript to: %s\n", absOutputDir)
30+
31+
// Parse services
32+
parser := NewParser(absServiceDir)
33+
services, err := parser.ParseServices()
34+
if err != nil {
35+
log.Fatal("Failed to parse services:", err)
36+
}
37+
38+
fmt.Printf("Found %d services\n", len(services))
39+
for _, svc := range services {
40+
fmt.Printf(" - %s (%d methods)\n", svc.Name, len(svc.Methods))
41+
}
42+
43+
// Generate TypeScript
44+
generator, err := NewGenerator(absOutputDir)
45+
if err != nil {
46+
log.Fatal("Failed to create generator:", err)
47+
}
48+
49+
if err := generator.Generate(services); err != nil {
50+
log.Fatal("Failed to generate TypeScript:", err)
51+
}
52+
53+
fmt.Println("✓ Generation complete!")
54+
}

0 commit comments

Comments
 (0)