Skip to content

v1.1.0 #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 3 additions & 67 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# browser [![MIT](https://img.shields.io/github/license/dineshgowda24/browser)](https://github.com/dineshgowda24/browser/blob/main/LICENSE) [![Go Reference](https://pkg.go.dev/badge/github.com/dineshgowda24/browser.svg)](https://pkg.go.dev/github.com/dineshgowda24/browser) [![Go report card](https://goreportcard.com/badge/github.com/dineshgowda24/browser)](https://goreportcard.com/report/github.com/dineshgowda24/browser) [![Build Status](https://dl.circleci.com/status-badge/img/circleci/MQTLZJuBejHgr2yqrojz3u/5NTLeuQeViQw2JaPQf7gKa/tree/main.svg?style=shield&circle-token=ab7a417fe410b8387c767f83568f7d2f2788ac4f)](https://dl.circleci.com/status-badge/redirect/circleci/MQTLZJuBejHgr2yqrojz3u/5NTLeuQeViQw2JaPQf7gKa/tree/main) [![Coverage](https://codecov.io/gh/dineshgowda24/browser/graph/badge.svg?token=XUA2VJW5FU)](https://codecov.io/gh/dineshgowda24/browser) [![X](https://img.shields.io/twitter/follow/_dineshgowda)](https://twitter.com/_dineshgowda)

<p align="center">
<img src="logo.png" width="125">
<img src="logo.png">
</p>

## Why?
Expand All @@ -21,73 +21,9 @@ I wanted a relatively extensible package that I could use in all the above use c

The ruby gem [fnando/browser](https://github.com/fnando/browser) inspires this package. I have used the gem in some of my previous projects and liked it. All the credit goes to the author of the ruby gem, who has done a great job.

## Design
## Documentation

I have kept the design as similar as possible to the gem but made changes where I felt necessary per the Go.

The following are the sub-packages:

- **matchers**: This package defines all the browser matchers.
- **devices**: This package defines all the device matchers.
- **platforms**: This package defines all the platform matchers.
- **bots**: This package defines all the bots matchers.

A `Matcher` interface defines a matching behaviour for a user agent string.

```go
type Matchers interface {
Match() bool // Match returns true if the user agent string matches the matcher.
Name() string // Name returns the name of the matcher.
}
```

### Matchers

`BrowserMatcher` interface matches the user agent string with the browser. Implement the `BrowserMatcher` interface to add a new browser.

```go
type BrowserMatcher interface {
Matcher
Version() string // Version returns the full version of the browser.
}
```

### Devices

`DeviceMatcher` interface matches the user agent string with the device. Implement the `DeviceMatcher` interface to add a new device.

```go
type DeviceMatcher interface {
Matcher
}
```

### Platforms

`PlatformMatcher` interface matches the user agent string with the platform. Implement the `PlatformMatcher` interface to add a new device.

```go
type PlatformMatcher interface {
Matcher
Version() string // Version returns the version of the platform.
}
```

### Bots

`BotMatcher` interface matches the user agent string with the bot. Implement the `BotMatcher` interface to add a new bot.

```go
type BotMatcher interface {
Matcher
}
```

### Browser Struct

`Browser` struct abstracts a lot of functionality. It uses the `BrowserMatcher`, `DeviceMatcher`, `PlatformMatcher` and `BotMatcher` interfaces to match the user agent string with the browser, device, platform and bot respectively. All the matchers are executed in the order they are defined in the `Browser` struct. The first matcher that returns `true` will be used.

A ton of helper functions are defined in the `Browser` struct to make it easy to use.
For detailed documentation visit [browser.dineshgowda.com](https://browser.dineshgowda.com). It has adoption guides, usage, contributing guidelines and also the list of all the matchers and browsers supported.

## Usage

Expand Down
14 changes: 14 additions & 0 deletions assets/test/matchers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ blackberry:
linux: Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.1.0.2342 Mobile Safari/537.10+(Linux LLC 1.2)
mac: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36@i-0bb1056c6cb023f60
windows: Mozilla/5.0 (Blackberry; U; BlackBerry 9900; en) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.1.0.346 Mobile Safari/534.11+ (Windows NT 6.2; WOW64; Trident/7.0; rv:11.0) like Gecko
brave:
linux: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) brave/0.7.7 Chrome/47.0.2526.73 Electron/0.36.2 Safari/537.36
mac: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) brave/0.7.7 Chrome/47.0.2526.73 Electron/0.36.3 Safari/537.36
windows: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) brave/0.7.7 Chrome/47.0.2526.73 Electron/0.36.2 Safari/537.36
chrome:
android: Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/_BuildID_) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36
ios: Mozilla/5.0 (iPhone; CPU iPhone OS 8_2 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) CriOS/44.0.2403.67 Mobile/12D508 Safari/600.1.4
Expand All @@ -31,6 +35,12 @@ electron:
linux: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Atom/1.1.0 Chrome/43.0.2357.65 Electron/0.30.7 Safari/537.36
mac: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Franz/0.9.8 Chrome/47.0.2526.110 Electron/0.36.9 Safari/537.36
windows: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.73 Electron/0.36.4 Safari/537.36
firefox:
android: Mozilla/5.0 (Android 8.0.0; Mobile; rv:59.0.2) Gecko/59.0.2 Firefox/59.0.2
ios: Mozilla/5.0 (iPhone; CPU iPhone OS 12_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/7.0.4 Mobile/16A404 Safari/605.1.15
linux: Mozilla/5.0 (Linux; U; Linux 2.6; en-US; rv:1.9.9.2) Gecko/20100722 Firefox/3.6.8
mac: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14_1; rv:64.0) Gecko/20100101 Firefox/64.0
windows: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.8) Gecko/20100722 AskTbBLT/3.8.0.12304 Firefox/3.6.8 GTB7.1
gsa:
android: Mozilla/5.0 (LINUX; ANDROID 4.4.2; LG-X135 Build/KOT49H.V10c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 MOBILE Safari/537.36 GSA/5.10.32.19.arm
ios: Mozilla/5.0 (iPhone; CPU iPhone OS 8_4_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) GSA/7.0.55539 Mobile/12H321 Safari/600.1.4
Expand Down Expand Up @@ -143,6 +153,10 @@ uc-browser:
unknown:
ios: Mozilla/30.0 (AppleCoreMedia/1.0.0.11B554a (iPad; U; CPU OS 7_0_4 like Mac OS X; en_us)
mac: QuickTime\x5Cxaa.7.0.4 (qtver=7.0.4;cpu=PPC;os=Mac 10.3.9)
vivaldi:
linux: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.86 Safari/537.36 Vivaldi/1.0.264.3
mac: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.43 Safari/537.36 Vivaldi/1.0.252.3
windows: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.75 Safari/537.36 Vivaldi/1.0.219.50
vivo-browser:
android: Mozilla/5.0 (Linux; Android 5.1.1; vivo V3Max A Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/38.0.0.0 Mobile Safari/537.36 VivoBrowser/5.0.10
ios: Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X; zh-CN) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/4.0 Chrome/38.0.0.0 Mobile Safari/537.36 VivoBrowser/5.0.10
Expand Down
101 changes: 69 additions & 32 deletions browser.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

const (
userAgentSizeLimit = 2048 // 2KB
userAgentSizeLimit = 2048 // 2KiB
)

// Matcher is an interface for user agent matchers.
Expand Down Expand Up @@ -66,38 +66,42 @@ func NewBrowser(userAgent string) (*Browser, error) {
// register registers the browser matcher detected from the user agent string.
func (b *Browser) register() {
// add all your browser matchers here, order matters
parser := matchers.NewUAParser(b.userAgent)
matchers := []BrowserMatcher{
matchers.NewAlipay(b.userAgent),
matchers.NewNokia(b.userAgent),
matchers.NewUCBrowser(b.userAgent),
matchers.NewBlackBerry(b.userAgent),
matchers.NewOpera(b.userAgent),
matchers.NewOtter(b.userAgent),
matchers.NewInstagram(b.userAgent),
matchers.NewSnapchat(b.userAgent),
matchers.NewWeibo(b.userAgent),
matchers.NewMicroMessenger(b.userAgent),
matchers.NewQQ(b.userAgent),
matchers.NewElectron(b.userAgent),
matchers.NewDuckDuckGo(b.userAgent),
matchers.NewGoogleSearchApp(b.userAgent),
matchers.NewHuaweiBrowser(b.userAgent),
matchers.NewKonqueror(b.userAgent),
matchers.NewMaxthon(b.userAgent),
matchers.NewMiuiBrowser(b.userAgent),
matchers.NewPaleMoon(b.userAgent),
matchers.NewPuffin(b.userAgent),
matchers.NewEdge(b.userAgent),
matchers.NewInternetExplorer(b.userAgent),
matchers.NewSamsungBrowser(b.userAgent),
matchers.NewSogouBrowser(b.userAgent),
matchers.NewVivoBrowser(b.userAgent),
matchers.NewSputnik(b.userAgent),
matchers.NewYaaniBrowser(b.userAgent),
matchers.NewYandex(b.userAgent),
matchers.NewChrome(b.userAgent), // chrome should be before safari
matchers.NewSafari(b.userAgent), // chrome and safari must be at the end
matchers.NewUnknown(b.userAgent),
matchers.NewAlipay(parser),
matchers.NewNokia(parser),
matchers.NewUCBrowser(parser),
matchers.NewBlackBerry(parser),
matchers.NewBrave(parser),
matchers.NewOpera(parser),
matchers.NewOtter(parser),
matchers.NewInstagram(parser),
matchers.NewSnapchat(parser),
matchers.NewWeibo(parser),
matchers.NewMicroMessenger(parser),
matchers.NewQQ(parser),
matchers.NewElectron(parser),
matchers.NewDuckDuckGo(parser),
matchers.NewGoogleSearchApp(parser),
matchers.NewHuaweiBrowser(parser),
matchers.NewKonqueror(parser),
matchers.NewMaxthon(parser),
matchers.NewMiuiBrowser(parser),
matchers.NewPaleMoon(parser),
matchers.NewPuffin(parser),
matchers.NewEdge(parser),
matchers.NewInternetExplorer(parser),
matchers.NewSamsungBrowser(parser),
matchers.NewSogouBrowser(parser),
matchers.NewVivaldi(parser),
matchers.NewVivoBrowser(parser),
matchers.NewSputnik(parser),
matchers.NewYaaniBrowser(parser),
matchers.NewYandex(parser),
matchers.NewFirefox(parser),
matchers.NewChrome(parser), // chrome should be before safari
matchers.NewSafari(parser), // chrome and safari must be at the end
matchers.NewUnknown(parser),
}

for _, matcher := range matchers {
Expand Down Expand Up @@ -204,6 +208,17 @@ func (b *Browser) IsBlackBerry() bool {
return false
}

// IsBrave returns true if the browser is Brave.
//
// https://brave.com/
func (b *Browser) IsBrave() bool {
if _, ok := b.getMatcher().(*matchers.Brave); ok {
return true
}

return false
}

// IsOpera returns true if the browser is Opera.
//
// https://www.opera.com/
Expand Down Expand Up @@ -296,6 +311,17 @@ func (b *Browser) IsElectron() bool {
return false
}

// IsFirefox returns true if the browser is Firefox.
//
// https://www.mozilla.org/firefox/
func (b *Browser) IsFirefox() bool {
if _, ok := b.getMatcher().(*matchers.Firefox); ok {
return true
}

return false
}

// IsDuckDuckGo returns true if the browser is DuckDuckGo.
//
// https://duckduckgo.com/
Expand Down Expand Up @@ -420,6 +446,17 @@ func (b *Browser) IsSougouBrowser() bool {
return false
}

// IsVivaldi returns true if the browser is Vivaldi.
//
// https://vivaldi.com/
func (b *Browser) IsVivaldi() bool {
if _, ok := b.getMatcher().(*matchers.Vivaldi); ok {
return true
}

return false
}

// IsVivoBrowser returns true if the browser is VivoBrowser.
//
// https://www.vivo.com/
Expand Down
60 changes: 60 additions & 0 deletions browser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -966,3 +966,63 @@ func TestBrowserIsEdge(t *testing.T) {
})
})
}

func TestBrowserIsFirefox(t *testing.T) {
Convey("Subject: #IsFirefox", t, func() {
Convey("When the browser is Firefox", func() {
Convey("It should return true", func() {
ua := testUserAgents["firefox"]
b, _ := NewBrowser(ua.Mac)
So(b.IsFirefox(), ShouldBeTrue)
})
})

Convey("When the browser is not Firefox", func() {
Convey("It should return false", func() {
ua := testUserAgents["chrome"]
b, _ := NewBrowser(ua.Windows)
So(b.IsFirefox(), ShouldBeFalse)
})
})
})
}

func TestBrowserIsBrave(t *testing.T) {
Convey("Subject: #IsBrave", t, func() {
Convey("When the browser is Brave", func() {
Convey("It should return true", func() {
ua := testUserAgents["brave"]
b, _ := NewBrowser(ua.Windows)
So(b.IsBrave(), ShouldBeTrue)
})
})

Convey("When the browser is not Brave", func() {
Convey("It should return false", func() {
ua := testUserAgents["chrome"]
b, _ := NewBrowser(ua.Windows)
So(b.IsBrave(), ShouldBeFalse)
})
})
})
}

func TestBrowserIsVivaldi(t *testing.T) {
Convey("Subject: #IsVivaldi", t, func() {
Convey("When the browser is Vivaldi", func() {
Convey("It should return true", func() {
ua := testUserAgents["vivaldi"]
b, _ := NewBrowser(ua.Windows)
So(b.IsVivaldi(), ShouldBeTrue)
})
})

Convey("When the browser is not Vivaldi", func() {
Convey("It should return false", func() {
ua := testUserAgents["chrome"]
b, _ := NewBrowser(ua.Windows)
So(b.IsVivaldi(), ShouldBeFalse)
})
})
})
}
45 changes: 23 additions & 22 deletions device.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,30 +42,31 @@ func NewDevice(userAgent string) (*Device, error) {

// register registers the device matcher detected from the user agent string
func (d *Device) register() {
parser := devices.NewUAParser(d.userAgent)
// define all your device matchers here
matchers := []DeviceMatcher{
devices.NewBlackberryPlaybook(d.userAgent),
devices.NewIphone(d.userAgent),
devices.NewIpad(d.userAgent),
devices.NewIpodTouch(d.userAgent),
devices.NewKindleFire(d.userAgent),
devices.NewKindle(d.userAgent),
devices.NewPlayStation3(d.userAgent),
devices.NewPlayStation4(d.userAgent),
devices.NewPlayStation5(d.userAgent),
devices.NewPSVita(d.userAgent),
devices.NewPSP(d.userAgent),
devices.NewWiiU(d.userAgent),
devices.NewWii(d.userAgent),
devices.NewXboxOne(d.userAgent), // this must be before xbox 360
devices.NewXbox360(d.userAgent),
devices.NewSwitch(d.userAgent),
devices.NewSurface(d.userAgent),
devices.NewKindle(d.userAgent),
devices.NewSamsung(d.userAgent),
devices.NewTV(d.userAgent), // this must be before android
devices.NewAndroid(d.userAgent),
devices.NewUnknown(d.userAgent),
devices.NewBlackberryPlaybook(parser),
devices.NewIphone(parser),
devices.NewIpad(parser),
devices.NewIpodTouch(parser),
devices.NewKindleFire(parser),
devices.NewKindle(parser),
devices.NewPlayStation3(parser),
devices.NewPlayStation4(parser),
devices.NewPlayStation5(parser),
devices.NewPSVita(parser),
devices.NewPSP(parser),
devices.NewWiiU(parser),
devices.NewWii(parser),
devices.NewXboxOne(parser), // this must be before xbox 360
devices.NewXbox360(parser),
devices.NewSwitch(parser),
devices.NewSurface(parser),
devices.NewKindle(parser),
devices.NewSamsung(parser),
devices.NewTV(parser), // this must be before android
devices.NewAndroid(parser),
devices.NewUnknown(parser),
}

for _, matcher := range matchers {
Expand Down
Loading