devicedetector

package module
v0.0.0-...-3e54d1f Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 14, 2026 License: LGPL-3.0 Imports: 14 Imported by: 0

README

DeviceDetector: Robust Device and Browser Detection

DeviceDetector is a precise and fast user agent parser and device detector written in Go, backed by the largest and most up-to-date user agent database. It is a port of the device_detector Rubygem, which is in turn a port of the Universal Device Detection Library.

DeviceDetector will parse any user agent and detect the browser, operating system, device used (desktop, tablet, mobile, tv, cars, console, etc.), brand and model. It detects thousands of user agent strings, even from rare and obscure browsers and devices.

DeviceDetector provides two caching modules: EmbeddedCache provides a cache based on the data set embedded in the package, while FilesystemCache allows you to use a data set shipped alongside your application.

A Note About the Code and Regular Expressions

This port does not aspire to be a one-to-one copy from the original code nor the Ruby gem, but rather an adaptation for the Go language.

Still, our goal is to use the original, unchanged regex yaml files, in order to mutually benefit from updates and pull request to both the original and the ported versions. This is probably not the cleanest code, and could perhaps be implemented better, and I welcome all pull requests.

Because Go uses the re2 regular expression parser, I was not able to compile most of the regular expressions with the standard library. For example, regular expressions in the device subtree had about a ~480 success rate out of 1200+ regexes. As such, I opted to use this pcre library wrapper as a replacement.

I know some would rather not add an extra dependency to their code, instead sticking to standard library features, but I am not a regular expressions guru, and thus do not even know if it is possible to programmatically modify the regexps to conform to re2.

I am also aware of the timing ramifications of using a replacement parser (Go provides certain timing guarantees with respect to re2), and I don't know if it really matters in a non-security context such as user agent parsing. If your thoughts differ, perhaps moving this functionality to a background process would be appropriate. In any case, I am open to discussions on how to solve this using re2; just submit an issue.

Installation

go get github.com/Insticator/device-detector
import (
  ...
  "github.com/Insticator/device-detector"
)

Usage

First, create a cache that can be used for any number of device detections:

// For embedded data cache: No need to distribute YAML files
// with your application.
cache, err := devicedetector.NewEmbeddedCache()
if err != nil {
  ...
}

// For filesystem data cache: You'll need to distribute the `regexes` directory
// with your application and point to it when creating the cache:
cache, err := devicedetector.NewFilesystemCache(filepath.Join(getApplicationPath(), "regexes"))
if err != nil {
  ...
}

In either case, NewXXCache will return a *Cache that you can pass to New() to avoid reloading and recompiling the regexes each time you want to check a new UserAgent.

The cache will compile the regexps the first time they are needed.

Pass the cache to any DeviceDetector instances you create:

userAgent := "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.17 Safari/537.36"

client, err := devicedetector.New(cache, userAgent)
if err != nil {
  ...
}

client.Name() // => "Chrome"
client.FullVersion() // => "30.0.1599.69"

client.OSName() // => "Windows"
client.OSVersion() // => "8"

// For many devices, you can also query the device name (usually the model name)
client.DeviceName() // => 'iPhone 5'
// Device types can be one of the following: desktop, smartphone, tablet,
// feature phone, console, tv, car browser, smart display, camera,
// portable media player, phablet, smart speaker, wearable, peripheral
client.DeviceType() // => 'smartphone'

DeviceDetector will return an empty string on all attributes, if the userAgent is unknown. You can make a check to ensure the client has been detected:

client.IsKnown() // => will return false if userAgent is unknown

Optionally DeviceDetector is using the content of Sec-CH-UA stored in the headers to improve the accuracy of the detection:

userAgent = "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.17 Safari/537.36"
headers := http.Header{}
headers.Add("Sec-CH-UA", `"Chromium";v="106", "Brave";v="106", "Not;A=Brand";v="99"`)

client, err := devicedetector.New(cache, userAgent, headers)
// error checking snipped

client.Name() // => "Brave"

Same goes with http-x-requested-with/x-requested-with:

userAgent := "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.17 Safari/537.36"
headers.Add("http-x-requested-with", "org.mozilla.focus")
client = devicedetector.New(cache, userAgent, headers)

client.Name() // => "Firefox Focus"

License

The original code is licensed under LGPL 3, and as we use regexes from that code, this project uses the same license.

The pcre library we use is on GitHub with a BSD license. However, as far as I know the code on which the fork is based does not appear to have a license at all. As there appear to be a few forks of this code floating around and several packages on the Go package index that use these forks, I am unsure as to whether this presents a legal issue or not, especially since it is primarily a wrapper around a C(++) API and therefore likely highly generic.

YMMV.

Contributing

Feel free to just submit a PR or issue and I'll see to it when I can. If your feedback includes code, please be sure to include tests.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Bot

type Bot struct {
	// contains filtered or unexported fields
}

func NewBot

func NewBot(cache *Cache, userAgent string) *Bot

func (*Bot) Category

func (b *Bot) Category() string

func (*Bot) IsBot

func (b *Bot) IsBot() bool

IsBot returns true if the userAgent given to NewBot represents a bot.

func (*Bot) Name

func (b *Bot) Name() string

type BotCache

type BotCache interface {
	Find(userAgent string) *CachedBot
}

type BrowserList

type BrowserList struct {
	// contains filtered or unexported fields
}

func (*BrowserList) Last

func (b *BrowserList) Last() hintBrowser

Last returns the last browser in the list.

func (*BrowserList) Reject

func (b *BrowserList) Reject(reject func(browser hintBrowser) bool) *BrowserList

Reject passes each member of the given BrowserList through a function and returns a new *BrowserList with only the entries where the func returned false.

type Cache

type Cache struct {
	Bot    BotCache
	Client ClientCache
	Device DeviceCache
	Hint   HintCache
	OS     OSCache
}

func NewEmbeddedCache

func NewEmbeddedCache() (*Cache, error)

type CacheFileList

type CacheFileList struct {
	// contains filtered or unexported fields
}

CacheFileList is a helper struct used to pass a list of caches to search into the cache's findRegex function.

func NewCacheFileList

func NewCacheFileList(baseList ...string) *CacheFileList

NewCacheFileList creates a new *CacheFileList, given the base list as an optional parameter. If no base is provided, uses the entires cacheFilenames array as a starting point.

func (*CacheFileList) Exclude

func (c *CacheFileList) Exclude(excludes ...string) *CacheFileList

Exclude returns a new *CacheFileList with the specified files removed.

func (*CacheFileList) Exclusive

func (c *CacheFileList) Exclusive(filenames ...string) *CacheFileList

Exclusive returns a copy of the *CacheFileList containing only the given filenames.

func (*CacheFileList) Get

func (c *CacheFileList) Get(index int) string

Get returns the filename at the given index.

func (*CacheFileList) Includes

func (c *CacheFileList) Includes(filename string) bool

Includes returns true if the given filename is in the *CacheFileList.

type CachedBot

type CachedBot struct {
	Regex string `yaml:"regex"`

	Name     string            `yaml:"name"`
	Category string            `yaml:"category"`
	URL      string            `yaml:"url"`
	Producer CachedBotProducer `yaml:"producer"`
	// contains filtered or unexported fields
}

type CachedBotProducer

type CachedBotProducer struct {
	Name string `yaml:"name"`
	URL  string `yaml:"url"`
}

type CachedClient

type CachedClient struct {
	Regex string

	Name    string
	Path    string
	Type    string
	Version string
	Engine  Engine
	URL     string
	// contains filtered or unexported fields
}

type CachedDevice

type CachedDevice struct {
	Regex string `yaml:"regex"`

	Type   string        `yaml:"device"`
	Name   string        `yaml:"model"`
	Models []CachedModel `yaml:"models"`
	Brand  string        `yaml:"-"`
	// contains filtered or unexported fields
}

func (*CachedDevice) FindModel

func (e *CachedDevice) FindModel(userAgent string) *CachedModel

findModel attempts to locate a matching model, if any.

type CachedDeviceList

type CachedDeviceList struct {
	// contains filtered or unexported fields
}

A DeviceList holds a list of devices loaded from YAML files.

func NewDeviceList

func NewDeviceList() *CachedDeviceList

NewDeviceList creates a new list of devices.

func (*CachedDeviceList) Append

func (d *CachedDeviceList) Append(name string, entry CachedDevice) *CachedDeviceList

Append returns a copy of the *CachedDeviceList with the given entry appended to the end of the list. If the new item already appears in the list, Append returns the original list unmodified.

func (*CachedDeviceList) Delete

func (d *CachedDeviceList) Delete(item string) *CachedDeviceList

Delete deletes an item from the *CachedDeviceList. If the item is not found, the original list is returned.

type CachedModel

type CachedModel struct {
	Regex string `yaml:"regex"`

	Brand string `yaml:"brand"`
	Name  string `yaml:"model"`
	Type  string `yaml:"device"`
	// contains filtered or unexported fields
}

type CachedOS

type CachedOS struct {
	Regex string

	Name     string
	Version  string
	Versions []CachedOSVersion
	// contains filtered or unexported fields
}

type CachedOSVersion

type CachedOSVersion struct {
	Regex   string
	Version string
}

type Client

type Client struct {
	// contains filtered or unexported fields
}

func NewClient

func NewClient(cache *Cache, userAgent string) *Client

func (*Client) Engine

func (c *Client) Engine() string

func (*Client) FullVersion

func (c *Client) FullVersion() string

func (*Client) IsBrowser

func (c *Client) IsBrowser() bool

func (*Client) IsKnown

func (c *Client) IsKnown() bool

func (*Client) Name

func (c *Client) Name() string

type ClientCache

type ClientCache interface {
	Find(userAgent string) *CachedClient
}

type ClientHint

type ClientHint struct {
	// contains filtered or unexported fields
}

func NewClientHint

func NewClientHint(cache *Cache, headers http.Header) *ClientHint

func (*ClientHint) Brands

func (c *ClientHint) Brands() map[string]string

func (*ClientHint) BrowserName

func (c *ClientHint) BrowserName() string

func (*ClientHint) IsAndroidApp

func (c *ClientHint) IsAndroidApp() bool

IsAndroidApp returns if we are an android app based on client hints.

func (*ClientHint) IsIridium

func (c *ClientHint) IsIridium() bool

func (*ClientHint) Mobile

func (c *ClientHint) Mobile() string

func (*ClientHint) Model

func (c *ClientHint) Model() string

func (*ClientHint) OSFamily

func (c *ClientHint) OSFamily() string

OSFamily returns the operating system family according to client hints.

func (*ClientHint) OSName

func (c *ClientHint) OSName() string

OSName returns the operating system name according to client hints.

func (*ClientHint) OSVersion

func (c *ClientHint) OSVersion() string

OSVersion returns the operating system version according to client hints.

func (*ClientHint) Platform

func (c *ClientHint) Platform() string

func (*ClientHint) PlatformVersion

func (c *ClientHint) PlatformVersion() string

type Device

type Device struct {
	// contains filtered or unexported fields
}

func NewDevice

func NewDevice(cache *Cache, userAgent string) *Device

NewDevice creates a new *Device.

func (*Device) Brand

func (d *Device) Brand() string

Brand returns the device brand

func (*Device) IsKnown

func (d *Device) IsKnown() bool

func (*Device) Name

func (d *Device) Name() string

Name returns the device name.

func (*Device) Type

func (d *Device) Type() string

Type returns the device type (desktop, smartphone...)

type DeviceCache

type DeviceCache interface {
	Delete(item string) DeviceCache
	Get(list string) *CachedDeviceList
	RegexesForHbbTV() *CacheFileList
	RegexesForShellTV() *CacheFileList
	RegexesForOthers() *CacheFileList
	// contains filtered or unexported methods
}

type DeviceDetector

type DeviceDetector struct {
	// contains filtered or unexported fields
}

func New

func New(cache *Cache, userAgent string, headers ...http.Header) *DeviceDetector

New returns a newly initialised *DeviceDetector.

func (*DeviceDetector) BotName

func (d *DeviceDetector) BotName() string

BotName returns the bot name or an empty string if client is not a bot.

func (*DeviceDetector) DeviceBrand

func (d *DeviceDetector) DeviceBrand() string

DeviceBrand returns the detected device brand.

func (*DeviceDetector) DeviceName

func (d *DeviceDetector) DeviceName() string

DeviceName returns the detected device name.

func (*DeviceDetector) DeviceType

func (d *DeviceDetector) DeviceType() string

DeviceType attempts to detect the device type.

func (*DeviceDetector) FullVersion

func (d *DeviceDetector) FullVersion() string

FullVersion returns the client version.

func (*DeviceDetector) IsBot

func (d *DeviceDetector) IsBot() bool

IsBot returns true if a bot is detected.

func (*DeviceDetector) IsKnown

func (d *DeviceDetector) IsKnown() bool

IsKnown returns true if this client is known.

func (*DeviceDetector) LocalForkIdentifier

func (d *DeviceDetector) LocalForkIdentifier() string

LocalForkIdentifier returns a unique identifier to verify this is the local fork

func (*DeviceDetector) Name

func (d *DeviceDetector) Name() string

Name returns the client name.

func (*DeviceDetector) OSFamily

func (d *DeviceDetector) OSFamily() string

OSFamily returns the operating system family.

func (*DeviceDetector) OSFullVersion

func (d *DeviceDetector) OSFullVersion() string

OSFullVersion returns the operating system version.

func (*DeviceDetector) OSName

func (d *DeviceDetector) OSName() string

OSName returns the operating system name.

type EmbeddedBotCache

type EmbeddedBotCache struct {
	// contains filtered or unexported fields
}

func NewEmbeddedBotCache

func NewEmbeddedBotCache() (*EmbeddedBotCache, error)

func (*EmbeddedBotCache) Find

func (b *EmbeddedBotCache) Find(userAgent string) *CachedBot

type EmbeddedClientCache

type EmbeddedClientCache struct {
	// contains filtered or unexported fields
}

func NewEmbeddedClientCache

func NewEmbeddedClientCache() (*EmbeddedClientCache, error)

func (*EmbeddedClientCache) Find

func (e *EmbeddedClientCache) Find(userAgent string) *CachedClient

type EmbeddedDeviceCache

type EmbeddedDeviceCache struct {
	// contains filtered or unexported fields
}

func NewEmbeddedDeviceCache

func NewEmbeddedDeviceCache() (*EmbeddedDeviceCache, error)

NewEmbeddedDeviceCache creates a new embedded device cache, whereby the device tree is loaded from resources contained within the package/application binary.

func (*EmbeddedDeviceCache) Delete

func (e *EmbeddedDeviceCache) Delete(list string) DeviceCache

func (*EmbeddedDeviceCache) Get

Get returns the devices with the associated list name.

func (*EmbeddedDeviceCache) RegexesForHbbTV

func (e *EmbeddedDeviceCache) RegexesForHbbTV() *CacheFileList

func (*EmbeddedDeviceCache) RegexesForOthers

func (e *EmbeddedDeviceCache) RegexesForOthers() *CacheFileList

func (*EmbeddedDeviceCache) RegexesForShellTV

func (e *EmbeddedDeviceCache) RegexesForShellTV() *CacheFileList

type EmbeddedHintCache

type EmbeddedHintCache struct {
	// contains filtered or unexported fields
}

func NewEmbeddedHintCache

func NewEmbeddedHintCache() (*EmbeddedHintCache, error)

func (*EmbeddedHintCache) Find

func (e *EmbeddedHintCache) Find(id string) string

type EmbeddedOSCache

type EmbeddedOSCache struct {
	// contains filtered or unexported fields
}

func NewEmbeddedOSCache

func NewEmbeddedOSCache() (*EmbeddedOSCache, error)

func (*EmbeddedOSCache) Find

func (e *EmbeddedOSCache) Find(userAgent string) *CachedOS

type Engine

type Engine struct {
	Default  string
	Versions map[string]string
}

type HintCache

type HintCache interface {
	Find(id string) string
}

type OS

type OS struct {
	// contains filtered or unexported fields
}

func NewOS

func NewOS(cache *Cache, userAgent string) *OS

NewOS returns a new *OS and tries to detect the OS of the given userAgent.

func (*OS) Family

func (o *OS) Family() string

Family returns the OS family

func (*OS) FullVersion

func (o *OS) FullVersion() string

FullVersion returns the full version string of the OS. Not implemented yet.

func (*OS) IsDesktop

func (o *OS) IsDesktop() bool

IsDesktop returns a best guess whether this OS is primarily a desktop OS.

func (*OS) Name

func (o *OS) Name() string

Name returns the full name of the OS.

func (*OS) Short

func (o *OS) Short() string

Short returns the short code for the OS.

func (*OS) ShortName

func (o *OS) ShortName() string

ShortName is an alias for Short(); it returns the short code for the OS.

type OSCache

type OSCache interface {
	Find(userAgent string) *CachedOS
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL