szepeviktor / composer-envato

Composer plugin for Envato 💚 Install WordPress themes and plugins from ThemeForest‎‎ and CodeCanyon
https://packagist.org/packages/szepeviktor/composer-envato
MIT License
32 stars 3 forks source link

Fix Envato package dist/download URL #15

Closed mcaskill closed 1 year ago

mcaskill commented 1 year ago

Resolves #2

Issue

When generating the Composer packages for the virtual repository from the configured list of Envato products, this plugin would retrieve the product's download URL via Envato's API and assign it as the package's dist URL. Composer uses this URL as the download's cache key and package URL in a project's composer.lock for later installations.

Unfortunately, this download URL is signed and expires quickly. This is problematic if ever Composer's cache is cleared or if the project is installed on a different server where Composer's cache is not mirrored.

Example

Given this configuration:

"config": {
    "envato": {
        "token": "…",
        "packages": {
            "envato/devowl-real-media-library": {
                "item-id": 13155134,
                "type": "wordpress-plugin"
            }
        }
    }
}

When this Envato product is installed, the plugin creates a Composer package and for its dist.url, it queries the Envato API for the download URL:

API Request URI:

https://api.envato.com/v3/market/buyer/download?item_id=13155134

The API responds with a temporary download URL:

https://marketplace-downloads.customer.envatousercontent.com/files/13155134/real-media-library-4.18.31.installable.zip?response-content-dispositio…

This results in the following entry in your project's composer.lock:

"packages": [
    {
        "name": "envato/devowl-real-media-library",
        "version": "4.18.31",
        "dist": {
            "type": "zip",
            "url": "https://marketplace-downloads.customer.envatousercontent.com/files/13155134/real-media-library-4.18.31.installable.zip?response-content-dispositio…"
        },
        "type": "wordpress-plugin"
    }
]

By default, Composer uses the dist.url as both the download URL and that download's cache key. When first retrieving this download URL from the Envato API, the URL is valid, the plugin is downloaded and cached under that same URL.

When the cached download is missing, Composer attempts to download the package from that same URL which is now expired. That's why the URL responds with "Authentication required".

To bypass this expired URL requires removing the package from the composer.lock to get Composer to ask the plugin for a (new) download URL.

Solution

This is resolved by assigning a more stable versioned dist URL (at this time, the API request URL to retrieve the download URL) and telling Composer that this plugin needs to update package download URLs before they are downloaded (extra.plugin-modifies-downloads).

Then, during the pre-file-download event, this plugin will retrieve the download URL from Envato's API while using the stable dist URL as the cache key.

Example

Using the same configuration as above, when the plugin creates a Composer package, its dist.url is now assigned the URL that queries Envato API for the download URL instead of the final download URL.

This results in the following entry in your project's composer.lock:

"packages": [
    {
        "name": "envato/devowl-real-media-library",
        "version": "4.18.31",
        "dist": {
            "type": "zip",
            "url": "https://api.envato.com/v3/market/buyer/download?item_id=13155134&version=4.18.31"
        },
        "type": "wordpress-plugin"
    }
]

This "static" dist.url, which also includes the package's version, is used as the cache key to ensure the cache is specific to each version.

When Composer attempts to download packages, this plugin will use the dist.url (if it matches the Envato API URI) to retrieve the temporary download URL and cache the ZIP against the cache key generated from the static URL.

Testing

I've also fixed some errors reported by PHPStan, from 6 down to 3:

------ ---------------------------------------------------------------------------------------------------------------------------
 Line   EnvatoConfig.php
------ ---------------------------------------------------------------------------------------------------------------------------
 53     Offset 'token' does not exist on array{token?: string, packages?: array{item-id: int|string|null, type: string|null}}.
 71     Offset 'packages' does not exist on array{token?: string, packages?: array{item-id: int|string|null, type: string|null}}.
 72     Offset 'packages' does not exist on array{token?: string, packages?: array{item-id: int|string|null, type: string|null}}.
------ ---------------------------------------------------------------------------------------------------------------------------

The last 3 should resolve themselves whenever this issue is fixed: phpstan/phpstan#8382.

szepeviktor commented 1 year ago

I don't understand the code but willing to merge.

mcaskill commented 1 year ago

I've updated the pull request with examples explaining the internal behaviour before and after the proposed changes.

szepeviktor commented 1 year ago

Thank you!