-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #92 from fastly/joeshaw/device-detection
- Loading branch information
Showing
9 changed files
with
357 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,9 @@ | ||
## Unreleased | ||
|
||
### Added | ||
|
||
- Add support for device detection (`device`) | ||
|
||
## 1.1.0 (2023-10-31) | ||
|
||
### Added | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# This file describes a Fastly Compute package. To learn more visit: | ||
# https://developer.fastly.com/reference/fastly-toml/ | ||
|
||
authors = ["oss@fastly.com"] | ||
description = "" | ||
language = "go" | ||
manifest_version = 2 | ||
name = "device-detection" | ||
service_id = "" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// Copyright 2022 Fastly, Inc. | ||
|
||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/fastly/compute-sdk-go/device" | ||
"github.com/fastly/compute-sdk-go/fsthttp" | ||
) | ||
|
||
func main() { | ||
fsthttp.ServeFunc(func(ctx context.Context, w fsthttp.ResponseWriter, r *fsthttp.Request) { | ||
d, err := device.Lookup(r.Header.Get("User-Agent")) | ||
if err != nil { | ||
fsthttp.Error(w, err.Error(), fsthttp.StatusInternalServerError) | ||
return | ||
} | ||
|
||
fmt.Fprintf(w, "%+v\n", d) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
// Package device provides device dection based on the User-Agent | ||
// header. | ||
package device | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
|
||
"github.com/fastly/compute-sdk-go/internal/abi/fastly" | ||
) | ||
|
||
var ( | ||
// ErrDeviceNotFound is returned when the device is not found. | ||
ErrDeviceNotFound = errors.New("device not found") | ||
|
||
// ErrUnexpected indicates that an unexpected error occurred. | ||
ErrUnexpected = errors.New("unexpected error") | ||
) | ||
|
||
type Device struct { | ||
info deviceInfo | ||
} | ||
|
||
type deviceInfo struct { | ||
Device struct { | ||
Name string `json:"name"` | ||
Brand string `json:"brand"` | ||
Model string `json:"model"` | ||
HWType string `json:"hwtype"` | ||
IsEReader bool `json:"is_ereader"` | ||
IsGameConsole bool `json:"is_gameconsole"` | ||
IsMediaPlayer bool `json:"is_mediaplayer"` | ||
IsMobile bool `json:"is_mobile"` | ||
IsSmartTV bool `json:"is_smarttv"` | ||
IsTablet bool `json:"is_tablet"` | ||
IsTVPlayer bool `json:"is_tvplayer"` | ||
IsDesktop bool `json:"is_desktop"` | ||
IsTouchscreen bool `json:"is_touchscreen"` | ||
} `json:"device"` | ||
} | ||
|
||
func Lookup(userAgent string) (Device, error) { | ||
var d Device | ||
|
||
raw, err := fastly.DeviceLookup(userAgent) | ||
if err != nil { | ||
status, ok := fastly.IsFastlyError(err) | ||
switch { | ||
case ok && status == fastly.FastlyStatusNone: | ||
return d, ErrDeviceNotFound | ||
case ok: | ||
return d, fmt.Errorf("%w (%s)", ErrUnexpected, status) | ||
default: | ||
return d, err | ||
} | ||
} | ||
|
||
if err := json.Unmarshal(raw, &d.info); err != nil { | ||
return d, err | ||
} | ||
|
||
return d, nil | ||
} | ||
|
||
// Name returns the name of the client device. | ||
func (d *Device) Name() string { | ||
return d.info.Device.Name | ||
} | ||
|
||
// Brand returns the brand of the client device, possibly different from | ||
// the manufacturer of that device. | ||
func (d *Device) Brand() string { | ||
return d.info.Device.Brand | ||
} | ||
|
||
// Model returns the model of the client device. | ||
func (d *Device) Model() string { | ||
return d.info.Device.Model | ||
} | ||
|
||
// HWType returns a string representation of the primary client platform | ||
// hardware. The most commonly used device types are also identified | ||
// via boolean variables. Because a device may have multiple device | ||
// types and this variable only has the primary type, we recommend using | ||
// the boolean variables for logic and using this string representation | ||
// for logging. | ||
func (d *Device) HWType() string { | ||
return d.info.Device.HWType | ||
} | ||
|
||
// IsEReader returns true if the client device is a reading device (like | ||
// a Kindle). | ||
func (d *Device) IsEReader() bool { | ||
return d.info.Device.IsEReader | ||
} | ||
|
||
// IsGameConsole returns true if the client device is a video game | ||
// console (like a PlayStation or Xbox). | ||
func (d *Device) IsGameConsole() bool { | ||
return d.info.Device.IsGameConsole | ||
} | ||
|
||
// IsMediaPlayer returns true if the client device is a media player | ||
// (like Blu-ray players, iPod devices, and smart speakers such as | ||
// Amazon Echo). | ||
func (d *Device) IsMediaPlayer() bool { | ||
return d.info.Device.IsMediaPlayer | ||
} | ||
|
||
// IsMobile returns true if the client device is a mobile phone. | ||
func (d *Device) IsMobile() bool { | ||
return d.info.Device.IsMobile | ||
} | ||
|
||
// IsSmartTV returns true if the client device is a smart TV. | ||
func (d *Device) IsSmartTV() bool { | ||
return d.info.Device.IsSmartTV | ||
} | ||
|
||
// IsTablet returns true if the client device is a tablet (like an | ||
// iPad). | ||
func (d *Device) IsTablet() bool { | ||
return d.info.Device.IsTablet | ||
} | ||
|
||
// IsTVPlayer returns true if the client device is a set-top box or | ||
// other TV player (like a Roku or Apple TV). | ||
func (d *Device) IsTVPlayer() bool { | ||
return d.info.Device.IsTVPlayer | ||
} | ||
|
||
// IsDesktop returns true if the client device is a desktop web browser. | ||
func (d *Device) IsDesktop() bool { | ||
return d.info.Device.IsDesktop | ||
} | ||
|
||
// IsTouchscreen returns true if the client device's screen is touch | ||
// sensitive. | ||
func (d *Device) IsTouchscreen() bool { | ||
return d.info.Device.IsTouchscreen | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# This file describes a Fastly Compute package. To learn more visit: | ||
# https://developer.fastly.com/reference/fastly-toml/ | ||
|
||
authors = ["oss@fastly.com"] | ||
description = "" | ||
language = "go" | ||
manifest_version = 2 | ||
name = "geolocation" | ||
service_id = "" | ||
|
||
|
||
[local_server] | ||
[local_server.device_detection] | ||
format = "inline-toml" | ||
|
||
[local_server.device_detection.user_agents] | ||
[local_server.device_detection.user_agents."Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20100101 Firefox/10.0 [FBAN/FBIOS;FBAV/8.0.0.28.18;FBBV/1665515;FBDV/iPhone4,1;FBMD/iPhone;FBSN/iPhone OS;FBSV/7.0.4;FBSS/2; FBCR/Telekom.de;FBID/phone;FBLC/de_DE;FBOP/5]"] | ||
device = {name = "iPhone", brand = "Apple", model = "iPhone4,1", hwtype = "Mobile Phone", is_ereader = false, is_gameconsole = false, is_mediaplayer = false, is_mobile = true, is_smarttv = false, is_tablet = false, is_tvplayer = false, is_desktop = false, is_touchscreen = true} | ||
|
||
[local_server.device_detection.user_agents."ghosts-app/1.0.2.1 (ASUSTeK COMPUTER INC.; X550CC; Windows 8 (X86); en)"] | ||
device = {name = "Asus TeK", brand = "Asus", model = "TeK", is_desktop = false} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
//go:build ((tinygo.wasm && wasi) || wasip1) && !nofastlyhostcalls | ||
|
||
// Copyright 2023 Fastly, Inc. | ||
|
||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/fastly/compute-sdk-go/device" | ||
"github.com/fastly/compute-sdk-go/fsthttp" | ||
"github.com/fastly/compute-sdk-go/fsttest" | ||
) | ||
|
||
func assert[T comparable](res fsthttp.ResponseWriter, field string, got, want T) { | ||
if got != want { | ||
fsthttp.Error(res, fmt.Sprintf("%s: got %v, want %v", field, got, want), fsthttp.StatusInternalServerError) | ||
} | ||
} | ||
|
||
func TestDeviceDetection(t *testing.T) { | ||
handler := func(ctx context.Context, res fsthttp.ResponseWriter, req *fsthttp.Request) { | ||
d, err := device.Lookup(req.Header.Get("User-Agent")) | ||
|
||
switch req.URL.Path { | ||
case "/iPhone": | ||
if err != nil { | ||
fsthttp.Error(res, err.Error(), fsthttp.StatusInternalServerError) | ||
return | ||
} | ||
|
||
assert(res, "Name", d.Name(), "iPhone") | ||
assert(res, "Brand", d.Brand(), "Apple") | ||
assert(res, "Model", d.Model(), "iPhone4,1") | ||
assert(res, "HWType", d.HWType(), "Mobile Phone") | ||
assert(res, "IsMobile", d.IsMobile(), true) | ||
assert(res, "IsTouchscreen", d.IsTouchscreen(), true) | ||
|
||
case "/AsusTeK": | ||
if err != nil { | ||
fsthttp.Error(res, err.Error(), fsthttp.StatusInternalServerError) | ||
return | ||
} | ||
|
||
assert(res, "Name", d.Name(), "Asus TeK") | ||
assert(res, "Brand", d.Brand(), "Asus") | ||
assert(res, "Model", d.Model(), "TeK") | ||
|
||
case "/unknown": | ||
if err != device.ErrDeviceNotFound { | ||
fsthttp.Error(res, err.Error(), fsthttp.StatusInternalServerError) | ||
return | ||
} | ||
|
||
default: | ||
fsthttp.Error(res, "not found", fsthttp.StatusNotFound) | ||
} | ||
} | ||
|
||
testcases := []struct { | ||
name string | ||
userAgent string | ||
}{ | ||
{ | ||
name: "iPhone", | ||
userAgent: "Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20100101 Firefox/10.0 [FBAN/FBIOS;FBAV/8.0.0.28.18;FBBV/1665515;FBDV/iPhone4,1;FBMD/iPhone;FBSN/iPhone OS;FBSV/7.0.4;FBSS/2; FBCR/Telekom.de;FBID/phone;FBLC/de_DE;FBOP/5]", | ||
}, | ||
|
||
{ | ||
name: "AsusTeK", | ||
userAgent: "ghosts-app/1.0.2.1 (ASUSTeK COMPUTER INC.; X550CC; Windows 8 (X86); en)", | ||
}, | ||
|
||
{ | ||
name: "unknown", | ||
userAgent: "whoopty doopty doo", | ||
}, | ||
} | ||
|
||
for _, tc := range testcases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
r, err := fsthttp.NewRequest("GET", "/"+tc.name, nil) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
r.Header.Set("User-Agent", tc.userAgent) | ||
w := fsttest.NewRecorder() | ||
|
||
handler(context.Background(), w, r) | ||
|
||
if got, want := w.Code, fsthttp.StatusOK; got != want { | ||
t.Errorf("got %v, want %v", got, want) | ||
t.Error(w.Body.String()) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters