-
Notifications
You must be signed in to change notification settings - Fork 578
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
feat: add http/user_agent #3387
Conversation
I was wondering if a class based approach is really necessary or if it should be done via a parse (and stringify) function instead, since there is no internal state and just getters and no setters. |
😕 There is internal state. Each property is lazily calculated and stored in the instance. In addition, there are special Also the class based approach aligns to other "http" things like |
There's also the option of using a module-private class as the implementation but only exporting a wrapper function that returns an instance of the class: class UserAgent {
// ...
}
export function parse(ua: string | null) {
return new UserAgent(ua);
} Has the advantage of slightly more flexibility: [ua1, ua2, ua3].map(parse); // works
[ua1, ua2, ua3].map(UserAgent); // Class constructor UserAgent cannot be invoked without 'new'
All of those are part of the web platform, though, so they're not necessarily comparable to stuff in the std libary. |
I had originally written it that way, but it was a non-sensical, because it was arbitrary abstraction with no value, and it is technically a lie, because
Very very trivially... [ua1, ua2, ua3].map((ua) => new UserAgent(ua)); If there was more to create an instance than call the constructor, I would be inclined to add it.
0k, then |
I think the difference is that all other classes in http have an internal state that is changeable via property or methods, or extend an existing class. What is the added value for UserAgent being a class instead of a plain object with lazy load getters? function parse(ua: string) {
let browser: Browser
let cpu: Cpu
let device: Device
let engine: Engine
let os: Os
return {
get browser(): Browser {
if (!browser) {
browser = {
name: undefined,
version: undefined,
major: undefined,
};
mapper(browser, ua, matchers.browser);
// deno-lint-ignore no-explicit-any
(browser as any).major = majorize(browser.version);
Object.freeze(browser);
}
return browser;
},
/** The architecture of the CPU extracted from the user agent string. */
get cpu(): Cpu {
if (!cpu) {
cpu = { architecture: undefined };
mapper(cpu, ua, matchers.cpu);
Object.freeze(cpu);
}
return cpu;
},
/** The model, type, and vendor of a device if present in a user agent
* string. */
get device(): Device {
if (!device) {
device = { model: undefined, type: undefined, vendor: undefined };
mapper(device, ua, matchers.device);
Object.freeze(device);
}
return device;
},
/** The name and version of the browser engine in a user agent string. */
get engine(): Engine {
if (!engine) {
engine = { name: undefined, version: undefined };
mapper(engine, ua, matchers.engine);
Object.freeze(engine);
}
return engine;
},
/** The name and version of the operating system in a user agent string. */
get os(): Os {
if (!os) {
os = { name: undefined, version: undefined };
mapper(os, ua, matchers.os);
Object.freeze(os);
}
return os;
},
};
} Null inputWhy is the input be allowed to be |
No, getter do not log. So a plain object with getters does not log to the console well.
What is the added value of it being a plan object with lazy load getters? Actually lazy load getters becomes more complex because you have to store the parsed value somewhere associated with the instance. You don't want to reparse on every property access. 😦
const ua = new UserAgent(request.headers.get("user-agent")); |
I guess there's some meta-commentary to be had around "should we even encourage user agent sniffing by adding this module to std?" The actual code and interface makes sense to me, just that lingering issue I guess. |
It would be incorrect to assume that the only purpose for parsing the user agent is to do "sniffing" instead of client side feature detection. There are a lot of "legitimate" reasons to parse the user agent, like reporting and analytics, improving user experience by defaulting a download to a specific platform/os build, understanding if the client is an embedded system/tablet/mobile device irrespective of device geometry, etc. |
I get that this makes it a bit more convenient in your example, but passing null to UserAgent makes no sense imo. const ua = new UserAgent(null) // this is valid... Instead the user could simply check if user-agent is present and an instance should be created in the first place or pass an empty string by themselves. const ua = request.headers.has("user-agent") ? new UserAgent(request.headers.get("user-agent")) : null; or const ua = new UserAgent(request.headers.get("user-agent") ?? "") This seems cleaner and more logical imo. |
Well it does imo. We will have to agree to disagree. |
That seems like an argument for making it more strict, not less — maybe it should throw on empty-string inputs. I don't see the use in deliberately allowing an API to represent nonsensical states — that's just inviting API consumers to write buggy code.
Even aside from the fact there are other plausible use cases (client-side parsing of |
It throwing would invalidate its most common use case... When processing a request and creating a "context" associated with that request,
Yes. |
Just wanted to voice my opinion here. I have to agree with @kitsonk on what the functionality should be. Seems like it is a lot more practical for the average use case. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay in review. LGTM
There are a lot of "legitimate" reasons to parse the user agent, like reporting and analytics, improving user experience by defaulting a download to a specific platform/os build, understanding if the client is an embedded system/tablet/mobile device irrespective of device geometry, etc.
I think this comment describes the role/purpose of this module well, and I'm in favor of landing this.
This feature adds user agent parsing to
std/http
. It is heavily inspired by ua-parser-js and is able to break down a user agent string into browser, OS, CPU, device, and browser engine.For example to respond with a request with some information extracted from the user agent: