cloudinary 2.1.1
cloudinary: ^2.1.1 copied to clipboard
Complete Cloudinary SDK for Dart and Flutter - upload, admin, search and signed delivery URLs, with no Flutter dependency.
Changelog #
2.1.1 #
- Docs: the README gains a Contributors section and a contributors badge, and its Contributing section points to Issues, Discussions and SECURITY.md.
- Docs: links to MIGRATION.md, CONTRIBUTING.md and the other repository files are absolute, so they work on pub.dev, which drops relative ones.
- Docs: every Dart block in the README is compiled by the test suite. The webhook example now calls
bodythe raw body, which is aString, not bytes, and the examples useconstwhere the analyzer asks for it. - Fixed: a
CLOUDINARY_URLthat is not a valid URI no longer puts the whole URL, API secret included, into the exception message, where anything that logged the error logged the secret. - Chore: the pubspec homepage is the live demo, and CI now requires 100% line coverage.
- Example: the demo is redesigned for every screen, from a 320-pixel phone to an ultrawide: the preview never scrolls away, the options sit in collapsible cards that summarise their values while closed, and wide screens give the options a panel of their own. The Dart that builds the URL gets a card of its own.
2.1.0 #
Changed #
- Dart SDK floor raised to
^3.13.0(Flutter 3.47 or newer), the current stable SDK, in line with this author's other packages. Projects on an older SDK keep resolving 2.0.0, which pub selects automatically; nothing in the API changed. - CI now also runs the test suite in a browser with dart2js and dart2wasm, and on the Dart 3.13.0 SDK floor.
- The example app is rebuilt as a demo of the whole package, live at https://nixrajput.github.io/cloudinary-dart: a delivery URL playground on Cloudinary's public demo cloud, a real unsigned upload, signing, webhook verification, search, every Admin API group,
CLOUDINARY_URLparsing and the exception family. It runs on the web, Android, iOS, macOS, Windows and Linux. - A new logo, in the same style as this author's other packages.
2.0.0 #
A rewrite. See MIGRATION.md for a call-by-call mapping from 1.x.
Added #
- Admin API, complete: resources, folders, tags, transformations, upload presets, upload mappings, streaming profiles, structured metadata fields and rules, usage, ping and config. Ten groups reached as
cloudinary.admin.<group>. - Search API with a chainable query builder, folder search, aggregations, cursor paging and signed cacheable search URLs.
- Delivery URL builder: transformations, chained transformations, private CDN distributions, CNAMEs, CRC-32 CDN subdomain sharding, SEO suffixes, short URLs, forced versions, signed URLs and
__cld_token__auth tokens. - Upload API completed:
explicit,rename,destroyByAssetId, tag and context commands, structured metadata updates,explode,multi,generateSprite,text, archives anddeleteByToken. SignatureProvider, so a client app can run signed uploads while the API secret stays on your server.- Webhook notification verification with a constant-time comparison and a configurable freshness window.
- Typed exceptions: a sealed
CloudinaryExceptionhierarchy covering API, rate limit, auth, not-found, transport, config and signature failures. - Typed response models, each exposing
rawso a response field this package does not model is still reachable. CLOUDINARY_URLsupport viaCloudinary.fromEnvironment()andCloudinary.fromUrl().- Automatic retries on 420, 429, 502, 503 and 504, honouring
Retry-After, configurable throughRetryPolicy.
Changed #
- Breaking: failures now throw instead of returning a response object carrying an
errorstring. - Breaking:
package:dioreplaced bypackage:http, and the barrel no longer re-exports the HTTP client, so its types are no longer part of this package's public API.ProgressCallbackbecomesCloudinaryProgressCallback. Anhttp.Clientcan be injected. - Breaking:
CloudinaryResponsesplit into one model per operation. - Breaking:
fileandfileBytesreplaced byCloudinaryFileSource, with.path,.bytesand.urlvariants. - Breaking:
optParamsrenamed toextraParams,progressCallbacktoonProgress. - Breaking: API grouped by family:
cloudinary.upload.*,cloudinary.admin.*,cloudinary.search.*,cloudinary.url.*. - Dart SDK floor raised to
^3.8.0, for sealed classes and null-aware elements. CloudinaryDeliveryTypeis now exported and its members extended. Its source file name was misspelled and never exported in 1.x.
Fixed #
- Array parameters are sent as repeated
key[]pairs, matching Cloudinary's own encoder. They were comma-joined, soadmin.resources.delete(['a', 'b'])asked Cloudinary to delete one asset literally nameda,band silently removed nothing. - Endpoints that take JSON bodies (
restore, related assets, folder rename) were form-encoded. transform()replaced the transformation chain instead of adding to it, silently dropping earlier stages.- Delivery URLs were signed before escaping, so the signature did not match the emitted path;
?and#were not escaped at all. - A URL suffix was appended to the delivery-type segments rather than replacing them with Cloudinary's plural form.
fetchand the other remote-source delivery types could not build a URL.crc32hashed UTF-16 code units, so a non-ASCII public ID picked a different CDN shard from every other SDK.- Context encoding escaped backslashes, which Cloudinary does not, corrupting any value containing one.
- A form whose values were all null crashed on a null check instead of sending an empty body.
multi,generateSpriteandtextomitted the resource-type segment Cloudinary'sapi_urlalways adds, so they hit the wrong route.- Streaming profile representations were sent as repeated form fields; Cloudinary takes one JSON string.
- Structured metadata values did not escape
", which Cloudinary requires there but not in contextual metadata. - A
.or..in a public ID survived into a delivery URL, where a CDN resolves it away and can reach a different product environment. - The transformation, format and URL suffix were interpolated into delivery URLs without escaping, so a
?could open a query string. - A folder search parsed its response as assets and an asset query could be turned into a folder request; the two are now coupled to their parsers.
- HTTP 420 was treated as a rate limit but excluded from the default retry set.
- A stale rate-limit reset or a non-positive
Retry-Afterretried immediately instead of backing off. - A repeated multipart field silently kept only the last value while the signature covered all of them.
- Webhook verification bounded only the lower end of the timestamp window, so a far-future timestamp stayed acceptable.
- Retries replayed POST and DELETE requests that may already have been applied. Only a 429, where the server states it did not act, repeats a non-idempotent request now.
- The per-attempt timeout covered only the response headers, so a peer that stalled mid-body hung the call indefinitely.
upload.deleteByTokenposted to/<resource_type>/delete_by_token, which answers 404 with an HTML body and leaves the asset in place. The route sits directly under the cloud name, so the method no longer takes a resource type. Verified end to end: an unsigned upload's token now deletes its asset.admin.streamingProfiles.createandupdatetookrepresentationsas optional. Cloudinary requires it on both, so omitting it produced a request that could only answer 400. Both now require it.upload.destroyByAssetIdtargetedDELETE /v1_1/<cloud>/asset/<id>, a route that does not exist and answers 404. It now postsasset_idto thedestroyendpoint, which is the shape the Upload API reference documents.admin.resources.updatetook contextual and structured metadata as pre-encoded strings while every other entry point took a map, so a value containing=or|silently corrupted the stored pair. Both now take a map and are escaped, as Cloudinary's ownupdateable_resource_paramsdoes.admin.resources.listaccepted aprefixwithout a delivery type, which Cloudinary always answers 400 for because it reads the type from the path. It is refused locally now, rather than spending an hourly-quota request to find out.- A response whose body failed mid-read discarded the status line and headers with it, so a 401 or a 429 the server had explicitly reported surfaced as an unclassified transport failure and a throttled POST refused to retry. The status now survives the read failure, which is named in the message; a 2xx still fails, because there the body is the result.
- A throttled Admin request ignored
X-FeatureRateLimit-Reset, burning its retries in milliseconds against an hourly quota. - A missing or unreadable upload file escaped as a raw
dart:ioerror rather than aCloudinaryException. - A
Mappassed throughextraParamswas serialized with Dart'stoStringand signed in that form; it is now rejected with an explanation. - A failed constructor left an HTTP client open with no object to close it on.
- A SEO URL suffix was included in the delivery signature payload, which Cloudinary excludes, so every signed SEO URL was rejected. The format extension now trails the suffix, and a suffix containing
.or/is rejected. - A transformation value containing a space produced a syntactically invalid URL.
transformChainstored the caller's chain by reference, so a latertransform()mutated a chain they still held.- Signed search URLs ignored
UrlConfig, pointing private-CDN and CNAME accounts at a host they do not serve from. - Signature timestamps were sent in milliseconds; Cloudinary expects UNIX seconds.
- Signature parameters were sorted by the joined
key=valuestring instead of by key, which produces a different digest whenever one parameter name is a prefix of another. destroyhad an inverted null check that threw a null-check error instead of its intended message.- Response getters mutated the object they were reading from.
- Errors were printed to stdout from library code.
- A dead HTTP client carrying credentials in its base URL was constructed on every request and never used.
- The analyzer
errors:block inanalysis_options.yamlsat at the top level instead of underanalyzer:, so none of its escalations had ever taken effect.
Security #
- Delivery URL paths reject
.and..segments. Dart'sUricollapses dot segments, so a..inside a caller-supplied public ID or folder path walked out of the/v1_1/<cloud>scope and re-aimed an authenticated Admin request at a different endpoint, with the API key and secret still attached. - The web secret guard now detects both web compilers. It used
identical(0, 0.0), which is true only under dart2js; under dart2wasm it read false and the guard never fired, so an API secret shipped in the bundle. It now usesbool.fromEnvironment('dart.library.js_interop')and lives in the private constructor that every factory routes through, soCloudinary.fromUrlandCloudinary.fromEnvironmentare covered too. - Auth tokens escape
!and the client IP.!separates ACL entries, so a!inside a caller-supplied identifier split one entry into several and could widen a token to every asset in the environment. A~inipcould splice an extra field into the signed token. - Signed delivery URLs no longer fall back silently. A public ID that was an absolute URL was returned untouched, discarding a requested signature and auth token and handing back a third-party origin; that now throws.
- Signature version 2 is now the default. Version 1 does not escape
&inside parameter values, so a value containing&is absorbed into the signed string as additional parameters. Version 1 remains selectable for compatibility. - Validation guards no longer use
assert. Dart strips asserts from release builds, so in a release Flutter build 1.x sent an unauthenticated request instead of failing when a signed method was called on an unsigned client. - Admin credentials moved out of the URL and into an
Authorization: Basicheader, so they no longer leak into logs and proxies. Cloudinary.signedrefuses to construct on a web runtime unlessallowSecretOnWeb: trueis passed, preventing an API secret from shipping in a browser bundle.CloudinaryConfig.toString()redacts the API secret.
1.2.2 #
- Chore: Added automated pub.dev release pipeline (version check, tag, publish); no API changes.
1.2.0 #
- Update:
LICENSEchanged to MIT. - Update: All dependencies are updated to latest version.
- Fix: All known bugs fixed.
1.1.3 #
- Update: All dependencies are updated to latest version.
- Fix: All known bugs fixed.
1.1.2 #
- Update: All dependencies are updated to latest version.
- Fix: All known bugs fixed.
1.1.1 #
- Update: All dependencies are updated to latest version.
- Fix: All known bugs fixed.
1.1.0 #
- Documentation: Updated documentation for the package.
- Fix: All known bugs fixed.
1.0.3 #
- Code cleanup and refactoring for better readability and maintainability.
- Package documentation updated.
1.0.2 #
- Code cleanup and refactoring for better readability and maintainability.
- Package documentation updated.
1.0.1 #
- Code cleanup and refactoring for better readability and maintainability.
- Package documentation updated.
- Project code formatted using
dartfmt.
1.0.0 #
- Initial version.