Skip to content
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

Types definition for v2 #272

Closed
alexandercerutti opened this issue Nov 19, 2018 · 13 comments
Closed

Types definition for v2 #272

alexandercerutti opened this issue Nov 19, 2018 · 13 comments

Comments

@alexandercerutti
Copy link
Contributor

alexandercerutti commented Nov 19, 2018

I was trying to create types definition for this lib, as I need them in a TS+Webpack project and with the intent of creating later a pull request (as types are missing and last issue got opened over 3 months ago).

I created the following typings, and it reflects the provided documentation. But when applying it to an app, I encounter a series of complications.

One of them is: we know that Bowser class is the one that gets exported. At least examples say this.
But if I do both

import bowser from 'bowser'
import * as bowser from 'bowser'

I cannot access both new Bowser or Bowser.getParser (and, so, to all the parser methods).

If I want to make it work, I have to do:

import * as bowser from "bowser";

So, what does the transpiled version export? It should be the same, but it seems to be not equal.
Thank you.

EDIT: If I have typings imported with

/// <reference path="typings/bowser.d.ts" />

(yes, I have a specific folder), but the typings below are not commented, I get this error:

Module '"bowser"' resolves to a non-module entity and cannot be imported using this construct. [2497]

========

This is the type definition that one corrected may be pushed if you want.

// Type definitions for Bowser v2
// Project: https://github.com/lancedikson/bowser
// Definitions by: Alexander P. Cerutti <https://github.com/alexandercerutti>

declare module "bowser" {
	/**
	 * Bowser class.
	 * Keep it simple as much as it can be.
	 * It's supposed to work with collections of {@link Parser} instances
	 * rather then solve one-instance problems.
	 * All the one-instance stuff is located in Parser class.
	 */
	class Bowser {
		constructor();

		/**
		 * Creates a {@link module:parser:Parser} instance
		 *
		 * @param {String} UA UserAgent string
		 * @param {Boolean} [skipParsing=false] same as skipParsing for {@link Parser}
		 * @returns {Parser}
		 * @throws {Error} when UA is not a String
		 *
		 * @example
		 * const parser = Bowser.getParser(window.navigator.userAgent);
		 * const result = parser.getResult();
		 */
		static getParser(UA: string, skipParsing?: boolean): Parser

		/**
		 * Creates a {@link Parser} instance and runs {@link Parser.getResult} immediately
		 *
		 * @param UA
		 * @return {ParsedResult}
		 *
		 * @example
		 * const result = Bowser.parse(window.navigator.userAgent);
		 */
		static parse(UA: string): Parser.ParsedResult
	}

	/**
	 * The main class that arranges the whole parsing process.
	 */

	class Parser {
		constructor(UA: string, skipParsing?: boolean);

		/**
		 * Get parsed browser object
		 * @return {Parser.BrowserDetails} Browser's details
		 */

		getBrowser(): Parser.BrowserDetails;

		/**
		 * Get browser's name
		 * @return {String} Browser's name or an empty string
		 */

		getBrowserName(): string;

		/**
		 * Get browser's version
		 * @return {String} version of browser
		 */

		getBrowserVersion(): string;

		/**
		 * Get OS
		 * @return {Parser.OSDetails} - OS Details
		 *
		 * @example
		 * this.getOS(); // {
		 * //	 name: 'macOS',
		 * //	 version: '10.11.12',
		 * // }
		 */

		getOS(): Parser.OSDetails;

		/**
		 * Get OS name
		 * @param {Boolean} [toLowerCase] return lower-cased value
		 * @return {String} name of the OS — macOS, Windows, Linux, etc.
		 */

		getOSName(toLowerCase?: boolean): string;

		/**
		 * Get OS version
		 * @return {String} full version with dots ('10.11.12', '5.6', etc)
		 */

		getOSVersion(): string;

		/**
		 * Get parsed platform
		 * @returns {Parser.PlatformDetails}
		 */

		getPlatform(): Parser.PlatformDetails;

		/**
		 * Get platform name
		 * @param {boolean} toLowerCase
		 */

		getPlatformType(toLowerCase?: boolean): string;

		/**
		 * Get parsed engine
		 * @returns {Parser.EngineDetails}
		 */

		getEngine(): Parser.EngineDetails;

		/**
		 * Get parsed result
		 * @return {Parser.ParsedResult}
		 */

		getResult(): Parser.ParsedResult;

		/**
		 * Get UserAgent string of current Parser instance
		 * @return {String} User-Agent String of the current <Parser> object
		 */

		getUA(): string;

		/**
		 * Is anything? Check if the browser is called "anything",
		 * the OS called "anything" or the platform called "anything"
		 * @param {String} anything
		 * @returns {Boolean}
		 */

		is(anything: any): boolean;

		/**
		 * Parse full information about the browser
		 */

		parse(): void;

		/**
		 * Get parsed browser object
		 * @returns {Parser.BrowserDetails}
		 */

		parseBrowser(): Parser.BrowserDetails;

		/**
		 * Get parsed engine
		 * @returns {Parser.EngineDetails}
		 */

		parseEngine(): Parser.EngineDetails;

		/**
		 * Parse OS and save it to this.parsedResult.os
		 * @returns {Parser.OSDetails}
		 */

		parseOS(): Parser.OSDetails;

		/**
		 * Get parsed platform
		 * @returns {Parser.PlatformDetails}
		 */

		parsePlatform(): Parser.PlatformDetails;

		/**
		 * Check if parsed browser matches certain conditions
		 *
		 * @param {Parser.checkTree} checkTree It's one or two layered object,
		 * which can include a platform or an OS on the first layer
		 * and should have browsers specs on the bottom-laying layer
		 *
		 * @returns {Boolean|undefined} Whether the browser satisfies the set conditions or not.
		 * Returns `undefined` when the browser is no described in the checkTree object.
		 *
		 * @example
		 * const browser = new Bowser(UA);
		 * if (browser.check({chrome: '>118.01.1322' }))
		 * // or with os
		 * if (browser.check({windows: { chrome: '>118.01.1322' } }))
		 * // or with platforms
		 * if (browser.check({desktop: { chrome: '>118.01.1322' } }))
		 */

		satisfies(checkTree: Parser.checkTree): boolean | undefined;

		/**
		 * Check if any of the given values satifies `.is(anything)`
		 * @param {string[]} anythings
		 * @returns {boolean} true if at least one condition is satisfied, false otherwise.
		 */

		some(anythings: string[]): boolean | undefined;

		/**
		 * Test a UA string for a regexp
		 * @param regex
		 * @returns {boolean} true if the regex matches the UA, false otherwise.
		 */

		test(regex: RegExp): boolean
	}

	namespace Parser {
		interface ParsedResult {
			browser: Details;
			os: OSDetails;
			platform: PlatformDetails;
			engine: Details;
		}

		interface Details {
			name?: string;
			version?: Array<{index: number, input: string} | boolean | string | any>;
		}

		interface OSDetails extends Details {
			versionName?: string;
		}

		interface PlatformDetails {
			type?: string;
			vendor?: string;
			model?: string;
		}

		type BrowserDetails = Details;
		type EngineDetails = Details;

		interface checkTree {
			[key: string]: any;
		}
	}

	class Utils {
		/**
		 * Get first matched item for a string
		 * @param {RegExp} regexp
		 * @param {String} ua
		 * @return {Array|{index: number, input: string}|*|boolean|string}
		 */
		static getFirstMatch(regexp: RegExp, ua: string): Array<{index: number, input: string} | boolean | string | any>;
		/**
		 * Get second matched item for a string
		 * @param regexp
		 * @param {String} ua
		 * @return {Array|{index: number, input: string}|*|boolean|string}
		 */
		static getSecondMatch(regexp: RegExp, ua: string): Array<{index: number, input: string} | boolean | string | any>;
		
		/**
		 * Match a regexp and return a constant or undefined
		 * @param {RegExp} regexp
		 * @param {String} ua
		 * @param {*} _const Any const that will be returned if regexp matches the string
		 * @return {*}
		 */
		static matchAndReturnConst(regexp: RegExp, ua: string, _const: any): any | undefined;
		
		/**
		 * Retrieves Windows commercial name from NT Core version name
		 * @param {string} version
		 * @returns {string | undefined}
		 */
		static getWindowsVersionName(version: string): string | undefined;
		
		/**
		 * Get version precisions count
		 *
		 * @example
		 *	 getVersionPrecision("1.10.3") // 3
		 *
		 * @param {string} version
		 * @return {number}
		 */
		static getVersionPrecision(version: string): number
		
		/**
		 * Calculate browser version weight
		 *
		 * @example
		 *	 compareVersions('1.10.2.1',	'1.8.2.1.90')	// 1
		 *	 compareVersions('1.010.2.1', '1.09.2.1.90');	// 1
		 *	 compareVersions('1.10.2.1',	'1.10.2.1');	// 0
		 *	 compareVersions('1.10.2.1',	'1.0800.2');	// -1
		 *	 compareVersions('1.10.2.1',	'1.10',	true);	// 0
		 *
		 * @param {String} versionA versions versions to compare
		 * @param {String} versionB versions versions to compare
		 * @param {boolean} [isLoose] enable loose comparison
		 * @return {Number} comparison result: -1 when versionA is lower,
		 * 1 when versionA is bigger, 0 when both equal
		 */
		static compareVersions(versionA: string, versionB: string, isLoose?: boolean): number;
		
		/**
		 * Array::map polyfill
		 *
		 * @param	{Array} arr
		 * @param	{Function} iterator
		 * @return {Array}
		 */
		static map(arr: Array<any>, iterator: Function): Array<any>
	}

	export = Bowser;
}
@alexandercerutti
Copy link
Contributor Author

Well, okay, I made more tests.
I discovered that to import Bowser in TS, you have to do

import Bowser = require("bowser");

Even if I use * as Bowser, it does not work.

I think now that now is just a matter of if the typings are correct. If you, @lancedikson, think they are correct, I will make a pull request. 👍

@Zefling
Copy link

Zefling commented Jan 3, 2019

Currently, I user this (for v1.9.4):

export type BrowserFlag = 'a' | 'b' | 'c'
    | 'android' | 'bada' | 'blackberry'
    | 'chrome' | 'chromium' | 'firefox' | 'gecko' | 'ios' | 'msie'
    | 'msedge' | 'opera' | 'phantom' | 'safari'
    | 'sailfish' | 'seamonkey' | 'silk' | 'tizen'
    | 'webkit' | 'webos' | 'chromeos' | 'iphone'
    | 'ipad' | 'ipod' | 'firefoxos' | 'linux' | 'mac'
    | 'touchpad' | 'windows' | 'windowsphone';

export interface BrowserInfo {
    name: string;
    osversion: string;
    version: string;

    /** grades a - This browser has full capabilities */
    a: boolean;
    /** grades b - This browser has degraded capabilities. */
    b: boolean;
    /** grades c - This browser has degraded capabilities. Serve simpler version */
    c: boolean;

    // engines
    android: boolean;
    bada: boolean;
    blackberry: boolean;
    chrome: boolean;
    chromium: boolean;
    firefox: boolean;
    gecko: boolean;
    ios: boolean;
    msie: boolean;
    msedge: boolean;
    opera: boolean;
    phantom: boolean;
    safari: boolean;
    sailfish: boolean;
    seamonkey: boolean;
    silk: boolean;
    tizen: boolean;
    webkit: boolean;
    webos: boolean;
    mobile: boolean;
    tablet: boolean;

    // operating systems
    chromeos: boolean;
    iphone: boolean;
    ipad: boolean;
    ipod: boolean;
    firefoxos: boolean;
    linux: boolean;
    mac: boolean;
    touchpad: boolean;
    windows: boolean;
    windowsphone: boolean;

    test(browserList: BrowserFlag[]): boolean;
    /**
     * Check if browser is supported
     *
     * @param [ua] user agent string
     */
    detect(userAgent: string): BrowserInfo;
    /**
     * Check if browser is supported
     *
     * @param minVersions map of minimal version to browser
     */
    check(browser: { [version: string]: string }): boolean;
    /**
     * Check if browser is supported
     *
     * @param minVersions map of minimal version to browser
     * @param [ua] user agent string
     */
    // tslint:disable-next-line:unified-signatures
    check(browser: { [version: string]: string }, userAgent: string): boolean;
    /**
     * Check if browser is supported
     *
     * @param minVersions map of minimal version to browser
     * @param [strictMode = false] flag to return false if browser wasn't found in map
     */
    // tslint:disable-next-line:unified-signatures
    check(browser: { [version: string]: string }, strict: boolean): boolean;
    /**
     * Check if browser is supported
     *
     * @param minVersions map of minimal version to browser
     * @param [strictMode = false] flag to return false if browser wasn't found in map
     * @param [ua] user agent string
     */
    // tslint:disable-next-line:unified-signatures
    check(browser: { [version: string]: string }, strict: boolean, userAgent: string): boolean;
    /**
     * Calculate browser version weight
     *
     * @example
     *   compareVersions(['1.10.2.1',  '1.8.2.1.90'])    // 1
     *   compareVersions(['1.010.2.1', '1.09.2.1.90']);  // 1
     *   compareVersions(['1.10.2.1',  '1.10.2.1']);     // 0
     *   compareVersions(['1.10.2.1',  '1.0800.2']);     // -1
     *
     * @param  versions versions to compare
     * @return comparison result
     */
    compareVersions(versions: string[]): number;
    /**
     * Check if browser is unsupported
     *
     * @example
     *   bowser.isUnsupportedBrowser({
     *     msie: "10",
     *     firefox: "23",
     *     chrome: "29",
     *     safari: "5.1",
     *     opera: "16",
     *     phantom: "534"
     *   });
     *
     * @param minVersions map of minimal version to browser
     * @param [strictMode = false] flag to return false if browser wasn't found in map
     * @param [ua] user agent string
     */
    isUnsupportedBrowser(minVersions: Object, strictMode?: Boolean, ua?: string): boolean;
}

declare var require: any;

export class Browser {

    private static _browser: any = undefined;

    static getInfo(): BrowserInfo {
        if (!Browser._browser) {
            Browser._browser = require('bowser');
        }
        return Browser._browser;
    }
}

@alexandercerutti
Copy link
Contributor Author

alexandercerutti commented Jan 5, 2019

@Zefling I cannot find in the code where BrowserInfo and BrowserFlags are defined. It seems like your typings are taken from v1 or sort of. I don't think they are valid anymore for v2.

Moreover, I don't even know if v2 is still an alpha or beta version.

@alexandercerutti
Copy link
Contributor Author

I've updated my version of typings and since for the comments I copied the code ones, I discovered that satisfies method still refers to .check() method, which does not exist in v2 (yet?).

@Zefling
Copy link

Zefling commented Jan 5, 2019

@alexandercerutti Oups, sorry, it's based on version 1.9.4. The V2 it's still in beta.

@alexandercerutti
Copy link
Contributor Author

alexandercerutti commented Jan 5, 2019

Okay, no problem. I putted a "down-thumb" reaction on your comment only to indicate that they are not good for v2.

@lancedikson
Copy link
Collaborator

Hi guys. I'm sorry for late response. Not always have enough time to arrange the open source stuff.

So, regarding these types definitions, I think they look great and I would appreciate if you send a PR.
Also, I should mention that I don't use the library that much and you can already be more aware of the types, then me. Btw, do you know if there is a way to auto-generate .d.ts files from jsdoc definitions? I was thinking, maybe this would make their support easier?

@alexandercerutti
Copy link
Contributor Author

alexandercerutti commented Jan 6, 2019

Great! I'll create a PR asap. 👍 Tomorrow I'll test them more on the project I'm using bowser in.

I heard about this topic some other times and saw few tools that create not-that-much reliable typings from Closure Compiler Docs or JSDocs, if I remember correctly. For me, it really depends on how many new methods you think you'll integrate, @lancedikson, and how many methods you still plan to integrate, since v2 is still a beta.

About the beta, is there any ETA about the stable releasing? Thank you!

Another thing I have to say is that all the typings I wrote above, are based on the code.

@alexandercerutti
Copy link
Contributor Author

alexandercerutti commented Jan 7, 2019

Okay, @lancedikson, before creating a PR, I tried to create a index.d.ts file in bowser folder.
As stated above, I had to use import Bowser = require("bowser");. This works fine if I import the typings with ///<reference path="" />.

But if I create the definition file as index.d.ts in main bowser folder, it won't be recognized.
Or better, to make it recognized, I have to set export default to Bowser class in the declaration file (and as it is in the sources) and then make import Bowser from "bowser";.

At this point, I can access to syntax, but when I use webpack with babel to compile all together, it gives me error as it cannot find Bowser as default export.

No, it does not work even with ///<reference path="" />

Moreover, I read that import x = require("bowser") is kept only for retro-compatibility with previous typescript versions and is maybe deprecated... I dunno...

I have to investigate more, because probably babel compilation to es5 does not make this.
I think that if we don't resolve this first, it is useless to make a PR for the typings as they will fail.

@alexandercerutti
Copy link
Contributor Author

I found that the only way it seems work, is using:

export = Bowser in the typings in index.d.ts and
import Bowser = require("bowser"); where we want to use it.

@lancedikson
Copy link
Collaborator

@alexandercerutti,

how many methods you still plan to integrate, since v2 is still a beta.

I don't have any other needed methods in my mind for now and it doesn't seem coming as requests from the community. Thus, I'd rather release the stable 2.0 version as soon as we got those typings defined based on current codebase. But, what stops me from it is that I can't be sure in the correctness of typings definition made by me, because I don't write on TS and don't know the right way to settle those. So, I'd need help with that here and thank you for providing it :)

I found that the only way it seems work, is using

I can't be sure here again as I don't use TS and don't know it's correct syntax. But, if you create the PR, I can participate in testing the typings in WebStorm and check if they're fully correct for JS files at least :)

@alexandercerutti
Copy link
Contributor Author

Okay, I'm going to create the pull request 👍

@alexandercerutti
Copy link
Contributor Author

Closing since the pull request has been merged!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants