Mini Kabibi Habibi

Current Path : C:/Program Files/Adobe/Adobe Photoshop 2025/Required/Plug-ins/Generator/crema.generate/
Upload File :
Current File : C:/Program Files/Adobe/Adobe Photoshop 2025/Required/Plug-ins/Generator/crema.generate/main.js

/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, node: true, plusplus: true, devel: true, nomen: true, indent: 4, bitwise: true, esversion: 6*/

(function () {
    "use strict";

    var PLUGIN_ID = "crema",
        GENERATE_ASSEST_PLUGIN_ID = "generator-assets",
        HOSTNAME = "127.0.0.1";


    /********************************************/

    var packageConfig = require("./package.json"),
        resolve = require("path").resolve,
        domain = require("domain"),
        http = require("http"),
        path = require("path"),
        fs = require("fs"),
        Q = require("q"),
        connect = require("connect"),
        morgan = require("morgan"),
        crypto = require("crypto"),
        AssetExtractor = require("./lib/asset-extractor"),
        Locale = require("locale"),
        MaxPixels = require("shared/MaxPixels"),
        globalLogger = require("shared/LogUtils"),
        PreviewRequestHandler = require("./lib/preview-request-handler"),
        PSDialogs = require("./lib/ps-dialogs"),
        PSEventStrings = require("./lib/ps-event-strings"),
        PreviewCache = require("./lib/preview-cache"),
        UserSettings = require("shared/UserSettings"),
        JSXRunner = require("./lib/JSXRunner"),
        Metadata = require("shared/Metadata"),
        QuickExport = require("./lib/quick-export"),
        Headlights = require("./lib/headlights"),
        WebSocketServer = require("ws").Server,
        Stream = require("stream"),
        Buffer = require("buffer").Buffer,
        allowConnections = false,
        _ = require("underscore");

    var mimeMap = {
            '.txt': 'text/plain',
            '.html': 'text/html',
            '.htm': 'text/html',
            '.css': 'text/css',
            '.xml': 'application/xml',
            '.json': 'application/json',
            '.js': 'application/javascript',
            '.jpg': 'image/jpeg',
            '.jpeg': 'image/jpeg',
            '.gif': 'image/gif',
            '.png': 'image/png',
            '.svg': 'image/svg+xml',
            '.woff': 'application/font-woff'
        };


    var _generator = null,
        _logger = null,
        _connCount = 0,
        _portNumber = null,
        _serverToken = null,
        _defaultSettings = null,
        _userSettings = null;

    function reportError(conn, message, id) {
        _logger.warn(message);
        
        if (conn.readyState === conn.OPEN) {
            conn.send(JSON.stringify({id: id, error: message}));
            return true;
        }
        return false;
    }

    function handleConnectionClose(conn) {
        AssetExtractor.deleteAllPreviewsForConnection(conn._cremaId);
    }

    function returnPromiseResults(promise, conn, id, traceLabel) {
        promise.then(function (result) {
            try {
                // we can have results as an array due to allsettled.
                // so we want to pull off the first and check its state.
                if (result.length) {
                    var invalid = _.find(result, function(promise) {
                        return promise.state == "rejected";
                    });
                    if (invalid) {
                        // If the rejection reason is an error, pull out the message and send it. Otherwise, just send
                        // the reason, whatever it is.
                        var error = invalid.reason,
                            errorMessage = error.message ? error.message : error;
                        conn.send(JSON.stringify({id: id, error: errorMessage}));
                        return;
                    }
                }
                conn.send(JSON.stringify({id: id, result: result}));
            } catch (ex) {
                _logger.warn(traceLabel + " exception: " + ex);
            }
        }).catch(function(e) {
            try {
                conn.send(JSON.stringify({id: id, error: e && e.message}));
            } catch (ex) {
                _logger.warn(ex);
            }
        });
    }

    function requestAccurateBounds(documentId, layerId) {
        return _generator.getPixmap(documentId, layerId, { boundsOnly: true }).get("bounds");
    }

    function addAuxiliaryInfoToDocinfo(docinfo) {
        var _homeDirectory = process.env[(process.platform === "win32") ? "USERPROFILE" : "HOME"],
            _desktopDirectory = _homeDirectory && path.resolve(_homeDirectory, "Desktop");

        docinfo._fileDirectory = docinfo.directory;
        if (!docinfo._fileDirectory || docinfo._fileDirectory === "." || docinfo.file.indexOf("/.Trashes/") !== -1) {
            docinfo._fileDirectory = _desktopDirectory;
        }
        docinfo._fileName = path.basename(docinfo.file);
        docinfo._fileExtension = path.extname(docinfo.file);
        docinfo._fileBaseName = (docinfo._fileExtension.length) ? docinfo._fileName.substr(0, docinfo._fileName.length - docinfo._fileExtension.length) : docinfo._fileName;

        docinfo._maxSupportedPixels = MaxPixels.getMaxPixels();

        if (_defaultSettings) {
            docinfo.defaultSettings = _.extend({}, _defaultSettings);
        }
    }

    function handleMessage(message, conn) {
        try {
            var m = JSON.parse(message),
                emitter,
                genAll,
                promise;
            //_logger.log("received message: %s", message);
            _logger.log("received command: %s", m.command);
            if (!m.hasOwnProperty("token") || m.token !== _serverToken) {
                _logger.error("closing connection: " + message);
                conn.close();
                return;
            }
            if (m.hasOwnProperty("command") && m.hasOwnProperty("id")) {
                switch (m.command) {
                case "docinfo":
                    /**
                     * Instead of calling down into PS to get the docinfo, this turns the generator-assets DOM into
                     * docinfo, since generator-assets is continually keeping an up to date version of its DOM from PS
                     * change events.
                     *
                     * The docinfo returned isn't 100% consistent with PS docinfo, but it's close and a good fit for our
                     * purposes. The only known differences include:
                     *
                     * 1) Selection is represented in docinfo._selectionById instead of docinfo.selection (which is by
                     *    layer index).
                     * 2) Layers do not have an index property.
                     */
                    AssetExtractor
                        .getActiveDocument()
                        .then(function (document) {
                            var docinfo = document.toRaw();
                            addAuxiliaryInfoToDocinfo(docinfo);
                            conn.send(JSON.stringify({ id: m.id, result: docinfo }));
                        })
                        .catch(function (e) {
                            _logger.log(e);
                            conn.send(JSON.stringify({id: m.id, error: "error getting doc info"}));
                        });
                    break;
                case "setDefaultSettings":
                    _defaultSettings = m.payload.defaultSettings;
                    _userSettings.set({ "defaultSettings": _defaultSettings});
                    conn.send(JSON.stringify({id: m.id, result: "success"}));
                    break;
                case "setDocSettings":
                    genAll = _generator.getDocumentSettingsForPlugin(null, PLUGIN_ID) || {};
                    genAll.docSettings = m.payload.docSettings;

                    _generator.setDocumentSettingsForPlugin(genAll, PLUGIN_ID);

                    // Explicitly invalidate the document in generator-assets document manager cache.
                    // PS does not emit an imageChanged event for these settings changes, so Export As's
                    // docInfo is left stale, overwriting settings on next invocation
                    AssetExtractor.invalidateActiveDocument();

                    conn.send(JSON.stringify({id: m.id, result: "success"}));
                    break;
                case "setLayerInfo":
                    if (_generator.setLayerSettingsForPlugin) {
                        _generator.setLayerSettingsForPlugin(m.payload.generatorSettings, m.payload.layerId, PLUGIN_ID);
                    } else {

                        genAll = _generator.getDocumentSettingsForPlugin(null, PLUGIN_ID) || {};
                        if (!genAll.layers) {
                            genAll.layers = {};
                        }
                        genAll.layers[m.payload.layerId] = m.payload.generatorSettings;

                        _generator.setDocumentSettingsForPlugin(genAll, PLUGIN_ID);
                    }
                    conn.send(JSON.stringify({id: m.id, result: "success"}));
                    break;
                case "getExactLayerBounds":
                    requestAccurateBounds(m.payload.docId, m.payload.layerId)
                        .then(function (rawBounds) {
                            conn.send(JSON.stringify({id: m.id, result: rawBounds}));
                        }, function (err) {
                            conn.send(JSON.stringify({id: m.id, error: "Error from getExactLayerBounds: " + err}));
                        });
                    break;
                case "getMaxPixels":
                    try {
                        conn.send(JSON.stringify({id: m.id, result: MaxPixels.getMaxPixels()}));
                    } catch (ex) {
                        reportError(conn, "Exception while returning getMaxPixels exception: " + ex);
                    }
                    break;
                case "exportComponents":
                    if (m.payload.components) {
                        promise = AssetExtractor.exportComponents(m.payload.components);
                        returnPromiseResults(promise, conn, m.id, "exportComponents");
                    } else {
                        conn.send(JSON.stringify({id: m.id, error: "no components"}));
                    }
                    break;
                case "generatePreview":
                    if (m.payload.component) {
                        var arrayOfChunks = [],
                            totalLength = 0,
                            stream = new Stream.Writable();
                        stream._write = function (chunk, encoding, next) {
                            arrayOfChunks.push(chunk);
                            totalLength += chunk.length;
                            next();
                        };
                        stream.on("finish", function () {
                            // Emulate fs stream so streamPixmap() resolves deferred object
                            stream.emit("close");
                        });
                        m.payload.component.stream = stream;
                        AssetExtractor
                            .generatePreview(m.payload.component, conn._cremaId)
                            .then(function (componentRenderedEvent) {
                                var arrayBuffer = Buffer.concat(arrayOfChunks, totalLength);
                                arrayOfChunks = [];
                                componentRenderedEvent.fileSize = totalLength;
                                PreviewCache.setBuffer(m.payload.component.id, arrayBuffer);
                                conn.send(JSON.stringify(componentRenderedEvent));
                            });
                    } else {
                        conn.send(JSON.stringify({id: m.id, error: "no component"}));
                    }
                    break;
                case "getComponentFileSize":
                    if (m.payload.component) {
                        promise = AssetExtractor.getComponentFileSize(m.payload.component);
                        returnPromiseResults(promise, conn, m.id, "getComponentFileSize");
                    } else {
                        conn.send(JSON.stringify({id: m.id, error: "no components"}));
                    }
                    break;
                default:
                    conn.send(JSON.stringify({id: m.id, error: "unknown command '" + m.command + "'"}));
                }
            } else {
                conn.send(JSON.stringify({error: "message format error"}));
            }
        } catch (e) {
            try {
                conn.send(JSON.stringify({error: "unknown message handling error"}));
            } catch (ex2) {
                _logger.warn(ex2);
            }
            _logger.error("Couldn't handle message %s: %s", message, e);
        }
    }

    function verifyHandshake(message, conn, activeConnId) {
        _logger.log("initial message recieved");
        var valid = false,
            id;
        try {
            var m = JSON.parse(message);
            valid = (m.hasOwnProperty("command") &&
                        m.hasOwnProperty("id") &&
                        m.hasOwnProperty("token") &&
                        m.command === "handshake" &&
                        m.token === _serverToken);
            id = m.id;
        } catch (e) {
            _logger.error("Couldn't handle message %s: %s", message, e);
        }

        if (valid) {
            conn.on("message", function (message) {
                handleMessage(message, conn);
            });
            conn.send(JSON.stringify({id: id, result: "success"}));
            allowConnections = true;
        } else {
            _logger.error("closing connection: " + message);
            conn.close();
        }
    }

    function isAddressLocalhost(address) {
        return (address === "localhost" || address === "127.0.0.1");
    }

    function originIsAllowed(origin, address) {
        return (origin === "file://" && isAddressLocalhost(address));
    }

    function verifyWebSocketClient(info) {
        return originIsAllowed(info.origin, info.req.connection.remoteAddress);
    }

    function handleWebSocketConnection(conn) {
        var id = _connCount++;

        _logger.log("Accepted websocket connection with ID %d", id);

        conn._cremaId = id;

        conn.on("error", function (err) {
            _logger.error("WebSocket connection with ID %d had error, closing: %s", err);
            conn.close();
            // TODO: Do we really have to call this here or can we just rely on the call in the close handler? (Multiple calls are harmless.)
            handleConnectionClose(conn);
        });

        conn.on("close", function () {
            _generator._logHeadlights("Crema WebSocket Closed");
            _logger.log("WebSocket connection with ID %d closed", id);
            handleConnectionClose(conn);
            allowConnections = false;
        });

        conn.once("message", function (message) {
            verifyHandshake(message, conn, String(id));
        });
    }

    function getServerToken() {
        var deferred = Q.defer();
        crypto.randomBytes(256, function (ex, buf) {
            var result =  buf.toString("hex");
            deferred.resolve(result);
        });
        return deferred.promise;
    }

    function parseCookies(cookieStr) {
        var cookiePairs = cookieStr.split(";"),
            cookies = {};
        cookiePairs.forEach(function (pair) {
            var splitPair = pair.split("="),
                key = splitPair[0],
                val = splitPair[1];
            if (key) {
                cookies[key] = val;
            }
        });
        return cookies;
    }

    function hasValidCremaTokenCookie(headers) {
        if(_.has(headers, "x-crema-token")) {
            return headers["x-crema-token"] === _serverToken;
        }
        var rawCookies = headers.cookie;
        if (!rawCookies) {
            return false;
        }
        var cookies = parseCookies(rawCookies);
        return cookies.crematoken === _serverToken;
    }

    function isValidResouceRequest(request) {
        return isAddressLocalhost(request.connection.remoteAddress) &&
                hasValidCremaTokenCookie(request.headers);
    }

    function respondWithUnknownError(response, err, msg) {
        try {
            _logger.error(msg, err);

            response.writeHead(500, {"Content-Type": "text/plain"});
            response.write("unknown error\n");
            response.end();
        } catch (er2) {
            _logger.warn("Error reporting error", er2);
        }
    }

    /* @param {Generator} generator The Generator instance for this plugin.
     * @param {object} config Configuration options for this plugin.
     * @param {Logger} logger The Logger instance for this plugin.
     */
    function _realInit(generator, config, logger) {
        var genConfig = {},
            ParserManager = require("generator-assets/lib/parsermanager"),
            parserManager;

        _generator = generator;
        _logger = logger;

        // Provide the generator logger to LogUtils
        globalLogger.init(logger);

        if (generator._config && generator._config["generator-assets"]) {
            genConfig = _.clone(generator._config["generator-assets"]);
        }

        //always enable meta-data for crema
        genConfig["meta-data-driven"] = true;
        genConfig["meta-data-root"] = "cremaPreview";
        genConfig["expand-max-dimensions"] = true;

        // Set the default jpg encoding.  This may eventually be obviated by a better default in g-a
        genConfig["use-jpg-encoding"] = "optimal";

        if (!_.has(genConfig, "use-flite")) {
            genConfig["use-flite"] = true;
        }

        parserManager = new ParserManager(genConfig);
        
        AssetExtractor.init(generator, genConfig, logger);

        Metadata.init(new JSXRunner(generator));

        _userSettings = new UserSettings(require("shared/UserSettings/SettingsFileInterface.js"));
        _defaultSettings = _userSettings.get("defaultSettings");

        var app = connect(),
            server = http.createServer(app),
            wss = new WebSocketServer({server: server, verifyClient: verifyWebSocketClient});

        wss.on("connection", handleWebSocketConnection);

        function tryToListen(server, port) {
            var deferred = Q.defer();

            server.on("error", function (e) {
                if (e.code === "EADDRINUSE") {
                    _logger.log("Address in use...");
                }
                deferred.reject(e);
            });
            server.listen(port, HOSTNAME, 511, function () {
                deferred.resolve(server.address().port);
            });

            return deferred.promise;
        }

        Q.all([tryToListen(server, 0), getServerToken()]).spread(function (port, token) {
            _portNumber = port;
            _serverToken = token;
            _generator.updateCustomOption(PLUGIN_ID, "cremaPluginConection", {port: _portNumber, token: _serverToken});
        }, function (e) {
            _logger.log("Listen rejected - code is " + e.code);
        });

        var getQuickExportOptions = function () {
            return {
                headlights: new Headlights(_generator),
                psDialogs: new PSDialogs(_generator),
                requestAccurateBoundsFunction: requestAccurateBounds,
                exportComponentsFunction: AssetExtractor.exportComponents.bind(AssetExtractor),
                metadataProvider: Metadata
            };
        };

        var runQuickExport = function (psEvent, document) {
            var quickExport = new QuickExport(getQuickExportOptions());
            return quickExport.run(psEvent, document.toRaw());
        };

        var handleQuickExport = function (psEvent) {
            AssetExtractor
                .getActiveDocument()
                .then(_.partial(runQuickExport, psEvent))
                .then(function (results) {
                    _logger.log("Quick exported:", results);
                })
                .catch(function (e) {
                    e = e || {};
                    _logger.error("Error running quick export:", e, e.stack);
                });
        };

        _generator.onPhotoshopEvent(PSEventStrings.QUICK_EXPORT_DOCUMENT, handleQuickExport);
        _generator.onPhotoshopEvent(PSEventStrings.QUICK_EXPORT_SELECTION, handleQuickExport);

        // Morgan is a logging middleware.  Only use it when the logger is in debug mode
        // It logs to stdout which we don't want in production environments
        if (_logger.manager.level === 5 ) {
            app.use(morgan("dev"));
        }

        app.use(function (request, response, next) {
            try {
                if (!isValidResouceRequest(request)) {
                    response.writeHead(403, {"Content-Type": "text/plain"});
                    response.end();
                    return;
                }

                var uri = request.url,
                    sURI = uri.toLowerCase(),
                    extname,
                    contentType;

                if (sURI.indexOf("?") > 0) {
                    sURI = sURI.substring(0, sURI.indexOf("?"));
                }

                extname = path.extname(sURI);
                contentType = mimeMap[extname] || 'text/html';

                if (sURI.indexOf("handshake") >= 0) {
                    response.writeHead(200, {"Content-Type": "text/plain", "Set-Cookie": "crematoken=" + _serverToken + "; SameSite=None; Secure"});
                    response.write("success\n");
                    response.end();
                } else if (sURI.indexOf("preview/") >= 0) {
                    var getPreviewFunction = AssetExtractor.getPreview.bind(AssetExtractor);
                    PreviewRequestHandler.handlePreviewRequest(sURI, response, contentType, getPreviewFunction);
                } else {
                    if (!allowConnections) {
                        response.writeHead(500, {"Content-Type": "text/plain"});
                        response.write("Websocket gone away, no longer accepting connection\n");
                        response.end();
                        return;
                    }
                    _logger.warn("Unexpected request sent to server");
                    response.writeHead(404, {"Content-Type": "text/plain"});
                    response.end();
                    return;
                }
            } catch (err) {
                respondWithUnknownError(response, err, "http uncaught error");
            }
        });
    }

    function init(generator, config, logger) {
        Q.onerror = function (err) {
            try {
                logger.error("Q uncaught errror", err);
            } catch (e) {
                logger.warn("can't even report uncaught error from Q");
            }
        };
        var mainDomain = domain.create();
        mainDomain.on("error", function (err) {
          // The error won't crash the process, but what it does is worse!
          // Though we've prevented abrupt process restarting, we are leaking
          // resources like crazy if this ever happens.
          // This is no better than process.on('uncaughtException')!
            logger.error("mainDomain uncaught errror", err);
        });
        mainDomain.run(function () {
            generator.getPhotoshopLocale()
                .then(function (locale) {
                    Locale.init(locale);
                    _realInit(generator, config, logger);
                });
        });
    }

    exports.init = init;

    // Private export of the AssetExtractor
    // Supports automated testing framework
    exports._assetExtractor = AssetExtractor;

}());