EXPRESSER UTILS

General network, IO, client and server utilities. As this module can’t reference any other module but Settings, all its logging will be done to the console only.

class Utils

    crypto = require "crypto"
    fs = require "fs"
    lodash = require "lodash"
    moment = require "moment"
    os = require "os"
    path = require "path"
    settings = require "./settings.coffee"

SERVER INFO UTILS

Helper to get the correct filename for general files. For example the settings.json file or cron.json for cron jobs. This will look into the current directory, the running directory and the root directory of the app. Returns null if no file is found. @param [String] filename The base filename (with extension) of the config file. @return [String] The full path to the config file if one was found, or null.

    getFilePath: (filename) ->
        originalFilename = "./" + filename.toString()

        if fs.existsSync?
            exists = fs.existsSync
        else
            exists = path.existsSync

Check if file exists.

        hasJson = exists filename
        return filename if hasJson

Try current path..

        filename = path.resolve __dirname, originalFilename
        hasJson = exists filename
        return filename if hasJson

Try parent path..

        filename = path.resolve __dirname, "../", originalFilename
        hasJson = exists filename
        return filename if hasJson

If file does not exist on local path, try application root path.

        filename = path.resolve path.dirname(require.main.filename), originalFilename
        hasJson = exists filename
        return filename if hasJson

Nothing found, so return null.

        return null

Returns a list of valid server IP addresses. If firstOnly is true it will return only the very first IP address found. @param [Boolean] firstOnly Optional, default is false which returns an array with all valid IPs, true returns a String will first valid IP. @return The server IPv4 address, or null.

    getServerIP: (firstOnly) ->
        ifaces = os.networkInterfaces()
        result = []

Parse network interfaces and try getting the server IPv4 address.

        for i of ifaces
            ifaces[i].forEach (details) ->
                if details.family is "IPv4" and not details.internal
                    result.push details.address

Return only first IP or all of them?

        if firstOnly
            return result[0]
        else
            return result

Return an object with general information about the server. @return [Object] Results with process pid, platform, memory, uptime and IP.

    getServerInfo: =>
        result = {}

Save parsed OS info to the result object.

        result.uptime = moment.duration(process.uptime, "s").humanize()
        result.hostname = os.hostname()
        result.title = path.basename process.title
        result.platform = os.platform() + " " + os.arch() + " " + os.release()
        result.memoryTotal = (os.totalmem() / 1024 / 1024).toFixed(0) + " MB"
        result.memoryUsage = 100 - (os.freemem() / os.totalmem() * 100).toFixed(0)
        result.loadAvg = os.loadavg()
        result.ips = @getServerIP()
        result.process = {pid: process.pid, memoryUsage: (process.memoryUsage().rss / 1024 / 1024).toFixed(0) + " MB"}

        return result

CLIENT INFO UTILS

Get the client or browser IP. Works for http and socket requests, even when behind a proxy. @param [Object] reqOrSocket The request or socket object. @return [String] The client IP address, or null.

    getClientIP: (reqOrSocket) ->
        return null if not reqOrSocket?

Try getting the xforwarded header first.

        if reqOrSocket.header?
            xfor = reqOrSocket.header "X-Forwarded-For"
            if xfor? and xfor isnt ""
                return xfor.split(",")[0]

Get remote address.

        if reqOrSocket.connection?
            return reqOrSocket.connection.remoteAddress
        else
            return reqOrSocket.remoteAddress

Get the client’s device. This identifier string is based on the user agent. @param [Object] req The request object. @return [String] The client’s device.

    getClientDevice: (req) ->
        ua = req.headers["user-agent"]

Find mobile devices.

        return "mobile-wp-8" if ua.indexOf("Windows Phone 8") > 0
        return "mobile-wp-7" if ua.indexOf("Windows Phone 7") > 0
        return "mobile-wp" if ua.indexOf("Windows Phone") > 0
        return "mobile-iphone-5" if ua.indexOf("iPhone5") > 0
        return "mobile-iphone-4" if ua.indexOf("iPhone4") > 0
        return "mobile-iphone" if ua.indexOf("iPhone") > 0
        return "mobile-android-5" if ua.indexOf("Android 5") > 0
        return "mobile-android-4" if ua.indexOf("Android 4") > 0
        return "mobile-android" if ua.indexOf("Android") > 0

Find desktop browsers.

        return "desktop-chrome" if ua.indexOf("Chrome/") > 0
        return "desktop-firefox" if ua.indexOf("Firefox/") > 0
        return "desktop-safari" if ua.indexOf("Safari/") > 0
        return "desktop-opera" if ua.indexOf("Opera/") > 0
        return "desktop-ie-11" if ua.indexOf("MSIE 11") > 0
        return "desktop-ie-10" if ua.indexOf("MSIE 10") > 0
        return "desktop-ie-9" if ua.indexOf("MSIE 9") > 0
        return "desktop-ie" if ua.indexOf("MSIE") > 0

Return default desktop value if no specific devices were found on user agent.

        return "desktop"

IO AND DATAUTILS

Copy the src file to the target, both must be the full file path. @param [String] src The full source file path. @param [String] target The full target file path.

    copyFileSync: (src, target) =>
        srcContents = fs.readFileSync src
        fs.writeFileSync target, srcContents

Minify the passed JSON value. Removes comments, unecessary white spaces etc. @param [String] source The JSON text to be minified. @param [Boolean] asString If true, return as string instead of JSON object. @return [String] The minified JSON, or an empty string if there’s an error.

    minifyJson: (source, asString) ->
        source = JSON.stringify source if typeof source is "object"
        index = 0
        length = source.length
        result = ""
        symbol = undefined
        position = undefined

Main iterator.

        while index < length

            symbol = source.charAt index
            switch symbol

Ignore whitespace tokens. According to ES 5.1 section 15.12.1.1, whitespace tokens include tabs, carriage returns, line feeds, and space characters.

                when "\t", "\r"
                , "\n"
                , " "
                    index += 1

Ignore line and block comments.

                when "/"
                    symbol = source.charAt(index += 1)
                    switch symbol

Line comments.

                        when "/"
                            position = source.indexOf("\n", index)

Check for CR-style line endings.

                            position = source.indexOf("\r", index)  if position < 0
                            index = (if position > -1 then position else length)

Block comments.

                        when "*"
                            position = source.indexOf("*/", index)
                            if position > -1

Advance the scanner’s position past the end of the comment.

                                index = position += 2
                                break
                            throw SyntaxError("Unterminated block comment.")
                        else
                            throw SyntaxError("Invalid comment.")

Parse strings separately to ensure that any whitespace characters and JavaScript-style comments within them are preserved.

                when "\""
                    position = index
                    while index < length
                        symbol = source.charAt(index += 1)
                        if symbol is "\\"

Skip past escaped characters.

                            index += 1
                        else break  if symbol is "\""
                    if source.charAt(index) is "\""
                        result += source.slice(position, index += 1)
                        break
                    throw SyntaxError("Unterminated string.")

Preserve all other characters.

                else
                    result += symbol
                    index += 1

Check if should return as string or JSON.

        if asString
            return result
        else
            return JSON.parse result

Helper to convert HSL colour to HEX. Used by Philips Hue and other colored lights. @param [Number] h The hue value. @param [Number] s The saturation value. @param [Number] l The brightness value. @return [String] The colour in HEX format.

    hslToHex: (h, s, l) ->
        x = h
        y = s
        z = 1.0 - x - y
        Y = l
        X = (Y / y) * x
        Z = (Y / y) * z
        r = X * 1.612 - Y * 0.203 - Z * 0.302
        g = -X * 0.509 + Y * 1.412 + Z * 0.066
        b = X * 0.026 - Y * 0.072 + Z * 0.962
        r = (if r <= 0.0031308 then 12.92 * r else (1.0 + 0.055) * Math.pow(r, (1.0 / 2.4)) - 0.055)
        g = (if g <= 0.0031308 then 12.92 * g else (1.0 + 0.055) * Math.pow(g, (1.0 / 2.4)) - 0.055)
        b = (if b <= 0.0031308 then 12.92 * b else (1.0 + 0.055) * Math.pow(b, (1.0 / 2.4)) - 0.055)
        cap = (x) -> Math.max 0, Math.min(1, x)

Helper to convert RGB to hex.

        rgbhex = (v) ->
            v = Math.round(v * 255)
            s = "0" + v.toString(16)
            return s.substr -2

Cap and transform RGB values.

        r = rgbhex cap r
        g = rgbhex cap g
        b = rgbhex cap b

Convert RGB to hex and return result.

        return "##{r}#{g}#{b}"

Singleton implementation

Utils.getInstance = ->
    @instance = new Utils() if not @instance?
    return @instance

module.exports = exports = Utils.getInstance()
h