https://github.com/ba0f3/is-sap-go
https://github.com/ba0f3/is-sap-go
Last synced: about 2 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/ba0f3/is-sap-go
- Owner: ba0f3
- Created: 2026-04-17T00:42:16.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-04-20T01:10:34.000Z (3 months ago)
- Last Synced: 2026-04-20T01:23:47.550Z (3 months ago)
- Language: Go
- Size: 31.3 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Security: SECURITY.md
Awesome Lists containing this project
README
# is-sap-go
**Detect Single Page Application frameworks from HTML responses**
[](https://github.com/ba0f3/is-sap-go/actions/workflows/ci.yml)
[](https://pkg.go/dev/github.com/ba0f3/is-sap-go)
[](https://goreportcard.com/report/github.com/ba0f3/is-sap-go)
---
`is-sap-go` inspects an HTTP response (or raw HTML) and determines whether a page is a **Single Page Application**, identifying the specific framework, meta-framework, and hosting platform with confidence scores.
## Features
- **30+ signatures** covering React, Vue, Angular, Svelte, Next.js, Nuxt, Remix, Astro, SvelteKit, and more
- **Confidence-weighted scoring** with hit multiplier for multiple matching signals
- **Redundancy suppression** — detects Next.js without also reporting React
- **Hosting detection** — Vercel, Netlify, Cloudflare Pages, GitHub Pages, Fly.io, Render
- **Optional JS sandbox** via [goja](https://github.com/dop251/goja) to extract `window.__*` globals from inline scripts
- **Zero HTTP dependencies** for the detection path — bring your own `*http.Client`
- **Functional options** API for clean configuration
## Install
```bash
go get github.com/ba0f3/is-sap-go
```
**Requires Go 1.22+**
## Quick Start
```go
package main
import (
"context"
"encoding/json"
"fmt"
"time"
sap "github.com/ba0f3/is-sap-go"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
result, err := sap.Detect(ctx, "https://nextjs.org",
sap.WithTimeout(10*time.Second),
sap.WithUserAgent("my-scanner/1.0"),
)
if err != nil {
panic(err)
}
data, _ := json.MarshalIndent(result, "", " ")
fmt.Println(string(data))
}
```
Output:
```json
{
"url": "https://nextjs.org",
"status_code": 200,
"is_spa": true,
"confidence": 0.95,
"frameworks": [
{
"name": "Next.js",
"category": "meta_framework",
"confidence": 0.95,
"signals": ["selector \"div#__next\"", "script src contains \"/_next/\""]
}
],
"hosting": [
{
"name": "Vercel",
"category": "hosting",
"confidence": 0.80,
"signals": ["header x-vercel-id: abc123"]
}
],
"headers": {
"Content-Type": ["text/html; charset=utf-8"],
"X-Vercel-Id": ["abc123"]
}
}
```
## API
### `Detect(ctx, url, ...Option) (*Result, error)`
Fetches the URL and runs all detection signatures.
### `DetectFromResponse(resp, body, ...Option) (*Result, error)`
Detects from a pre-fetched `*http.Response` and its body bytes.
### `DetectFromHTML(html, headers, ...Option) (*Result, error)`
Lowest-level entry point. Analyzes raw HTML string and `http.Header`.
### Options
| Option | Type | Default | Description |
|---|---|---|---|
| `WithTimeout` | `time.Duration` | `10s` | HTTP request timeout |
| `WithUserAgent` | `string` | `"is-sap-go/1.0"` | User-Agent header |
| `WithMaxBodyBytes` | `int64` | `2 MiB` | Max response body to read |
| `WithFollowRedirects` | `bool` | `true` | Follow HTTP 3xx redirects |
| `WithEnableJSEval` | `bool` | `false` | Enable goja JS sandbox |
| `WithHTTPClient` | `*http.Client` | *(new)* | Custom HTTP client |
## Detection Methodology
Each **signature** defines a framework name, category, weight, and a list of **matchers**:
| Weight | Meaning |
|---|---|
| `0.95` | Definitive signal (e.g., `#__next` selector for Next.js) |
| `0.6–0.8` | Strong signal (e.g., `data-reactroot` for React) |
| `0.3–0.5` | Weak/generic signal (e.g., `div#app` for SPA generic) |
Multiple matching signals increase the score via a hit multiplier. Confidence is computed as `1 − e^(−score)` and capped at `1.0`. When a meta-framework (e.g., Next.js) is detected, implied frameworks (React) are suppressed unless they have independent high-confidence signals.
### Categories
| Category | Description |
|---|---|
| `spa_framework` | Frontend libraries: React, Vue, Angular, Svelte, etc. |
| `meta_framework` | Full-stack frameworks: Next.js, Nuxt, Remix, Astro, etc. |
| `hosting` | Platform detection: Vercel, Netlify, Cloudflare Pages, etc. |
| `bundler` | Build tools: Webpack, Vite, esbuild, etc. |
### Matcher Types
| Matcher | Description |
|---|---|
| `HeaderContains` | Response header contains substring |
| `HeaderRegex` | Response header matches regex |
| `AnyHeader` | Response header is present |
| `CookieNameHas` | Cookie with given name exists |
| `HTMLSubstring` | Body contains substring (case-insensitive) |
| `HTMLRegex` | Body matches regex |
| `Selector` | CSS selector matches elements |
| `SelectorAttr` | Element matching selector has attribute containing substring |
| `AttrExists` | Any element has the given attribute |
| `ScriptSrcContains` | `` contains substring |
| `LinkHrefContains` | `<link href>` contains substring |
| `MetaName` | `<meta name>` content contains substring |
| `JSGlobal` | JS sandbox found `window.__key` global |
| `Any(ms...)` | At least one matcher fires |
| `All(ms...)` | All matchers must fire |
## Running Tests
```bash
# Unit tests (offline)
go test -v -race -short ./...
# Integration tests (requires network)
go test -v -run TestIntegration -timeout 120s ./...
```
## Example CLI
```bash
go run examples/cmd/main.go https://nextjs.org
```
## License
MIT