EMI INDO's Forum Posts

  • The Firebase Sparsha plugin at least has the Web client ID option in Google Cloud Console (OAuth 2.0 → Credentials).

    usually forms com.googleusercontent.apps.XXXXXXXXXX....

    if there is no OAuth 2.0 option like the Firebase Sparsha plugin only returns the token ID (idToken) for Firebase authentication, not the serverAuthCode that PlayFab needs for Google OAuth validation on the PlayFab server.

  • you must lock your project in SDK v1 with the end url rxxx

    example: editor.construct.net/r440-2

    If there are new features either from the SDK or from the editor above the rxxxx you locked, you cannot use them.

  • Yes, now you can test it, is it working?

    const testProperty = properties[0]; // index 0
    // group 
    const appName = properties[2]; // index 2
    const appVersion = properties[3]; // index 3
    const appDescription = properties[4]; // index 4
    const permissions = properties[5]; // index 5
    
    console.log("testProperty: ", testProperty);
    console.log("appName: ", appName);
    console.log("appVersion: ", appVersion);
    console.log("appDescription: ", appDescription);
    console.log("permissions: ", permissions);
    
  • Try Construct 3

    Develop games in your browser. Powerful, performant & highly capable.

    Try Now Construct 3 users don't see these ads
  • {
    
    
    	"languageTag": "en-US",
    	"fileDescription": "Strings for DOMMessaging.",
    	"text": {
    		"plugins": {
    			"mycompany_dommessaging": {
    				"name": "MyDOMPlugin",
    				"description": "An example third-party plugin using DOM messaging.",
    				"help-url": "https://www.construct.net",
    				"properties": {
    					"test-property": {
    						"name": "Test property",
    						"desc": "A test number property."
    					},
    					"app-config": {
    						"name": "app-config",
    						"desc": "app-config."
    					},
    					"app-name": {
    						"name": "app-name",
    						"desc": "app-name."
    					},
    					"app-version": {
    						"name": "app-version",
    						"desc": "app-version"
    					},
    					"app-description": {
    						"name": "app-description",
    						"desc": "app-description"
    					},
    					"permissions": {
    						"name": "permissions",
    						"desc": "permissions"
    					},
    					"layout-config": {
    						"name": "layout-config",
    						"desc": "layout-config"
    					},
    					"popup-layout": {
    						"name": "popup-layout",
    						"desc": "popup-layout"
    					},
    					"options-layout": {
    						"name": "options-layout",
    						"desc": "options-layout"
    					},
    					"background-layout": {
    						"name": "background-layout",
    						"desc": "background-layout"
    					}
    				},
    				"aceCategories": {
    					"custom": "Custom"
    				},
    				"conditions": {
    				},
    				"actions": {
    					"set-document-title": {
    						"list-name": "Set document title",
    						"display-text": "Set document title to [b]{0}[/b]",
    						"description": "Set the document title to a string.",
    						"params": {
    							"title": {
    								"name": "Title",
    								"desc": "The document title to set."
    							}
    						}
    					}
    				},
    				"expressions": {
    					"get-document-title": {
    						"description": "Get the current document title.",
    						"translated-name": "DocumentTitle"
    					}
    				}
    			}
    		}
    	}
    }
    
    
    
    const SDK = globalThis.SDK;
    
    ////////////////////////////////////////////
    // The plugin ID is how Construct identifies different kinds of plugins.
    // *** NEVER CHANGE THE PLUGIN ID! ***
    // If you change the plugin ID after releasing the plugin, Construct will think it is an entirely different
    // plugin and assume it is incompatible with the old one, and YOU WILL BREAK ALL EXISTING PROJECTS USING THE PLUGIN.
    // Only the plugin name is displayed in the editor, so to rename your plugin change the name but NOT the ID.
    // If you want to completely replace a plugin, make it deprecated (it will be hidden but old projects keep working),
    // and create an entirely new plugin with a different plugin ID.
    const PLUGIN_ID = "MyCompany_DOMMessaging";
    ////////////////////////////////////////////
    
    const PLUGIN_CATEGORY = "general";
    
    const PLUGIN_CLASS = SDK.Plugins.MyCompany_DOMMessaging = class MyCustomPlugin extends SDK.IPluginBase
    {
    	constructor()
    	{
    		super(PLUGIN_ID);
    		
    		SDK.Lang.PushContext("plugins." + PLUGIN_ID.toLowerCase());
    		
    		this._info.SetName(globalThis.lang(".name"));
    		this._info.SetDescription(globalThis.lang(".description"));
    		this._info.SetCategory(PLUGIN_CATEGORY);
    		this._info.SetAuthor("Scirra");
    		this._info.SetHelpUrl(globalThis.lang(".help-url"));
    		this._info.SetIsSingleGlobal(true);
    		this._info.SetRuntimeModuleMainScript("c3runtime/main.js");
    
    		// Set the domSide.js script to run in the context of the DOM
    		this._info.SetDOMSideScripts(["c3runtime/domSide.js"]);
    		
    		SDK.Lang.PushContext(".properties");
    		
    		this._info.SetProperties([
    			new SDK.PluginProperty("integer", "test-property", 0),
    			new SDK.PluginProperty("group", "app-config"), // [???] - Should show "Application Configuration"
     new SDK.PluginProperty("text", "app-name", "My Application"), // [???] - Should show "Application Name"
     new SDK.PluginProperty("text", "app-version", "1.0.0"), // [???] - Should show "Application Version"
     new SDK.PluginProperty("longtext", "app-description", "A web application built with Construct 3"), // [???]
     new SDK.PluginProperty("text", "permissions", "storage,activeTab"), // [???] - Should show "Permissions"
    
     new SDK.PluginProperty("group", "layout-config"), // [???] - Should show "Layout Configuration"
     new SDK.PluginProperty("text", "popup-layout", ""), // [???] - Should show "Popup Layout"
     new SDK.PluginProperty("text", "options-layout", ""), // [???] - Should show "Options Layout"
     new SDK.PluginProperty("text", "background-layout", "") // [???] - Should show "Background Layout"
    		]);
    		
    		SDK.Lang.PopContext();		// .properties
    		
    		SDK.Lang.PopContext();
    	}
    };
    
    PLUGIN_CLASS.Register(PLUGIN_ID, PLUGIN_CLASS);
    
    
    
    
    
  • I tested your code no problem, try to remove the addon from addon manager, and add it back.

  • For example

    github.com/Scirra/Construct-Addon-SDK/blob/main/plugin-sdk/v2/domMessagingPlugin/lang/en-US.json

    "properties": {
    					"test-property": {
    						"name": "Test property",
    						"desc": "A test number property."
    					}
    				},
    
    
    this._info.SetProperties([
    			new SDK.PluginProperty("integer", "test-property", 0)
    		]);
    
  • construct.net/en/make-games/addons/1452/document-file

    1. New action: Base64 src element id
    2. New expression: File extension
    3. New Select file - show -save.c3p

    NOTE c3p

    • Select file image/* > sprite load string base64]
    • Select file audio/* > html elm id load string base64
    • Select file video/* > html elm id load string base64
    • Download blob to SAF
    • Handle large files, so that applications do not crash, run out of memory
  • construct.net/en/make-games/addons/1452/document-file

    1. New Downlaod file with URL.c3p
    2. Fix response action: Download file from url
    Subscribe to Construct videos now
  • construct.net/en/make-games/addons/1453/webrct/versions

    1. Update c3p
    2. New action Apply Filter
    3. New action Apply Effect
    4. Fix android save Video Recording to SAF

    Screenshot

    Subscribe to Construct videos now
  • Supports c3 build service = YES

    Support platform

    1. Android = YES
    2. IOS = NO
    3. Browser = YES only webrct

    Download addon Document file

    Download addon WebRTC

    Description plugin Document file

    Action

    If Android denies permission to access non-public file paths, such as files inside the WhatsApp folder, use the action "Copy file to cache."

    Note: The action "Show overlay all audio list" may also be denied by Android if the clicked item comes from a non-public folder, even if permission has already been granted. In this case, also use the "Copy file to cache" action.

    Important: The "Copy file to cache" action may consume significant device storage. If necessary, use the "Clear app old cache" action to free up space.

    Condition

    every action list is triggered if an error occurs, the condition is in the ON ERROR (COMBO) category.

    then get the response from the Expression name Error message.

    Expression

    Upload file to server

    Save this code as an upload.php file on your server. This script already handles CORS, validation, security, and generates the reply URL dynamically.

    • This works immediately without changing anything
    • or modify upload.php to your liking.
    <?php
    // ======================================================================
    // 1. CORS HANDLING (MUST BE AT THE VERY TOP)
    // ======================================================================
    $origin = $_SERVER['HTTP_ORIGIN'] ?? '*';
    header('Access-Control-Allow-Origin: ' . $origin);
    header('Access-Control-Allow-Methods: POST, OPTIONS');
    header('Access-Control-Allow-Headers: Content-Type');
    header('Access-Control-Allow-Credentials: true');
    
    // Respond to preflight requests
    if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
     http_response_code(204);
     exit();
    }
    
    // ======================================================================
    // 2. HELPER FUNCTIONS
    // ======================================================================
    
    /**
     * Send a JSON response and terminate execution.
     * @param bool $success Operation status.
     * @param array $data Data to include in the response.
     * @param int $httpCode HTTP status code.
     */
    function sendJsonResponse(bool $success, array $data, int $httpCode = 200) {
     http_response_code($httpCode);
     header('Content-Type: application/json');
     echo json_encode(['success' => $success] + $data);
     exit();
    }
    
    /**
     * Translate PHP file upload error codes into human-readable messages.
     * @param int $errorCode The error code from $_FILES['file']['error'].
     * @return string Error message.
     */
    function handleUploadError(int $errorCode): string {
     switch ($errorCode) {
     case UPLOAD_ERR_INI_SIZE:
     case UPLOAD_ERR_FORM_SIZE:
     return 'File size exceeds the limit.';
     case UPLOAD_ERR_PARTIAL:
     return 'The file was only partially uploaded.';
     case UPLOAD_ERR_NO_FILE:
     return 'No file was uploaded.';
     case UPLOAD_ERR_NO_TMP_DIR:
     return 'Missing a temporary folder on the server.';
     case UPLOAD_ERR_CANT_WRITE:
     return 'Failed to write file to disk.';
     case UPLOAD_ERR_EXTENSION:
     return 'A PHP extension stopped the file upload.';
     default:
     return 'An unknown upload error occurred.';
     }
    }
    
    // ======================================================================
    // 3. MAIN LOGIC WITH CENTRALIZED ERROR HANDLING
    // ======================================================================
    
    try {
     // Ensure this is a POST request
     if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
     throw new Exception('Only POST method is allowed.', 405);
     }
    
     // Validate required parameters
     if (empty($_POST['userId']) || empty($_POST['extension']) || !isset($_FILES['file'])) {
     throw new Exception('Missing required parameters (userId, extension, or file).', 400);
     }
    
     // Check for upload errors
     if ($_FILES['file']['error'] !== UPLOAD_ERR_OK) {
     throw new Exception(handleUploadError($_FILES['file']['error']), 500);
     }
    
     // Sanitize userId and extension from the client
     $userId = preg_replace('/[^a-zA-Z0-9_-]/', '', $_POST['userId']);
    
     // Create user directory if it doesn't exist
     $mainUploadDir = 'uploads/';
     $userUploadDir = $mainUploadDir . $userId . '/';
     if (!is_dir($userUploadDir) && !mkdir($userUploadDir, 0755, true)) {
     throw new Exception('Failed to create user directory. Check server permissions.', 500);
     }
    
     // Obtain file information from the server side (more reliable)
     $originalFileName = basename($_FILES['file']['name']);
    
     // Further sanitize the file name for security
     $safeFileName = preg_replace('/[^a-zA-Z0-9-_.]/', '', $originalFileName);
     $serverExtension = strtolower(pathinfo($safeFileName, PATHINFO_EXTENSION));
    
     // ## CLIENT-SIDE EXTENSION VALIDATION REMOVED AS PER REQUEST ##
    
     // File type validation (whitelist) remains for security
     $allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'mp3', 'mp4', 'm4a', 'aac', 'webm'];
     if (!in_array($serverExtension, $allowedExtensions)) {
     throw new Exception('File type "' . $serverExtension . '" is not allowed.', 415);
     }
    
     // Validate file size
     $maxFileSize = 20 * 1024 * 1024; // 20 MB
     if ($_FILES['file']['size'] > $maxFileSize) {
     throw new Exception('File size exceeds the 20MB limit.', 413);
     }
    
     // Generate a unique new file name
     $newFileName = time() . '_' . $safeFileName;
     $targetFilePath = $userUploadDir . $newFileName;
    
     // Move the file to permanent location
     if (!move_uploaded_file($_FILES['file']['tmp_name'], $targetFilePath)) {
     throw new Exception('Failed to save the uploaded file on the server.', 500);
     }
    
     // If successful, send a success response in JSON format
     $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
     $host = $_SERVER['HTTP_HOST'];
     $finalFileUrl = $protocol . '://' . $host . '/' . $targetFilePath;
    
     sendJsonResponse(true, [
     'message' => 'File uploaded successfully.',
     'url' => $finalFileUrl,
     'fileName' => $newFileName
     ]);
    
    } catch (Exception $e) {
     // Handle all errors in one place and send a JSON response
     sendJsonResponse(false, ['error' => $e->getMessage()], $e->getCode());
    }
    
    
    
    

    Old documentation

    Simple example c3p: Video Recording and Take Capture image

    select file, upload, download, will return a response

    Response (Success):

    Object: A JSON object containing metadata of the successfully.

    • uri (String): The content URI (content://) of the saved file.
    • traditionalPath (String, optional): The traditional file system path (e.g., /storage/emulated/0/Music/test.mp3)
    • name (String): The display name of the file.
    • size (Number): The size of the file in bytes.
    • sizeReadable (String): The file size in a human-readable format. (e.g., "1.23 MB").
    • mimeType (String): The MIME type of the file.
    • lastModified (Number): The last modified timestamp of the file in milliseconds.
    • isDirectory (Boolean): true if it's a directory, false if it's a file.
    • base64: Base64-encoded string from the contents of a stored or selected file, or from an upload/download response.

    (provided that permission has been granted)

    You can handle, manipulate, image, audio, video, pdf, and other files, whatever the user chooses on their device, to be rendered into HTML elements, or files processed by your own hosting server, or an AI server.

    The response from the server can be directly downloaded to the user's device, or just rendered to the HTML element.

    The upload, download or response file select will return metadata for use as needed, (provided permission has been granted),

    If the user denies permission, the metadata will not be obtained, the process on the android device will fail.

    all processes on the user's device must go through the SAF URI path, not the traditional file system path traditional file system path (e.g., /storage/emulated/0/Download/filename.pdf).

    For certain categories of apps only

    developer.android.com/training/data-storage/manage-all-files

    1. File managers
    2. Backup and restore apps
    3. Anti-virus apps
    4. Document management apps
    5. On-device file search
    6. Disk and file encryption
    7. Device-to-device data migration

    Screenshot of example permissions (Video Recording and Take Capture image)

    NOTE sensitive permissions

    Subscribe to Construct videos now
  • Constant777

    You can use this addon construct.net/en/make-games/addons/1452/document-file

    I will make an example later,

    after the review is completed/approved.

    github.com/Scirra/Construct-bugs/issues/8649

  • Plugin Meta Audience Network.

    construct.net/en/game-assets/addons/meta-audience-network-383

    1. create new c3addon 1/May/2025
    2. The old c3addon is no longer in use.
    3. c3addon is recreated with typescript.
    Subscribe to Construct videos now
  • Build Failed - Applovin Plugin Problem c3 build service

    github.com/EMI-INDO/c3addon-bug/issues/13

    1. Update c3addon v1.0.5.9 to v1.0.6.9
    2. FIX error c3 build service

    construct.net/en/game-assets/addons/applovin-max-ads-1847

  • Sorry, I meant when exporting there is an option whether Edge-to-Edge wants to be enabled or not.

    It's okay if I enable it with the local build only.