diff --git a/docs/pages/cldimage/configuration.mdx b/docs/pages/cldimage/configuration.mdx index 98981014..631e6c65 100644 --- a/docs/pages/cldimage/configuration.mdx +++ b/docs/pages/cldimage/configuration.mdx @@ -1617,7 +1617,7 @@ Allows configuration for the Cloudinary environment. ```jsx copy config={{ cloud: { - cloudName: 'my-cloud' + cloudName: '', } }} ``` diff --git a/docs/pages/clduploadwidget/configuration.mdx b/docs/pages/clduploadwidget/configuration.mdx index d1b873c6..fd96d860 100644 --- a/docs/pages/clduploadwidget/configuration.mdx +++ b/docs/pages/clduploadwidget/configuration.mdx @@ -17,7 +17,7 @@ import Table from '../../components/Table'; # CldUploadWidget Configuration -## Configuration +## Basic Props ({`{(options) => {}}`}), }, - { - prop: 'options', - type: 'object', - example: () => ({`{encryption: {...}}`}), - more: () => (More Info) - }, { prop: 'signatureEndpoint', type: 'string', @@ -80,21 +74,69 @@ A function that returns a React component that receives instance methods and opt ``` -### `options` +#### Parameters -Parameters used to customize and configure the Upload Widget instance. These options are passed in -directly to the Upload Widget, exposing all available parameters through the `options` object. +By passing in a function as the child of the Upload Widget, you're able to gain access to the widget, results, and instance methods to interface directly with the widget. + +
(See Instance Methods below) + }, + ]} +/> + +**Example** ```jsx copy showLineNumbers -options={{ - sources: ['local', 'url', 'unsplash'], - multiple: true, - maxFiles: 5 -}} + + {({ cloudinary, widget, open }) => { + // Return UI + }} + ``` -[Learn more about Upload Widget parameters](https://cloudinary.com/documentation/upload_widget_reference#parameters) on the Cloudinary docs. - ### `signatureEndpoint` An API endpoint used to sign the parameters generated during upload. @@ -116,6 +158,8 @@ uploadPreset="my-upload-preset" ## Events +### Callback Functions +
(See Instance Methods below) - }, - ]} -/> - -**Example** - -```jsx copy showLineNumbers - - {({ cloudinary, widget, open }) => { - // Return UI - }} - -``` - ## Instance Methods The Upload Widget exposes instance methods that gives the ability to have greater control and flexbility over @@ -515,4 +496,77 @@ They're exposed either through event handler callback or child function paramete description: 'Updates a widget currently in memory with new options.' }, ]} -/> \ No newline at end of file +/> + +## Advanced + +### Configuration + +
({`{ url: { cloudName: 'spacejelly' } }`}), + more: () => (More Info) + }, + { + prop: 'options', + type: 'object', + example: () => ({`{encryption: {...}}`}), + more: () => (More Info) + }, + ]} +/> + +#### `config` + +Allows configuration for the Cloudinary environment. + +**Examples** + +```jsx copy +config={{ + cloud: { + cloudName: '', + apiKey: '' + } +}} +``` + +#### `options` + +Parameters used to customize and configure the Upload Widget instance. These options are passed in +directly to the Upload Widget, exposing all available parameters through the `options` object. + +```jsx copy showLineNumbers +options={{ + sources: ['local', 'url', 'unsplash'], + multiple: true, + maxFiles: 5 +}} +``` + +[Learn more about Upload Widget parameters](https://cloudinary.com/documentation/upload_widget_reference#parameters) on the Cloudinary docs. \ No newline at end of file diff --git a/docs/pages/cldvideoplayer/configuration.mdx b/docs/pages/cldvideoplayer/configuration.mdx index a28b0948..cdd1db5e 100644 --- a/docs/pages/cldvideoplayer/configuration.mdx +++ b/docs/pages/cldvideoplayer/configuration.mdx @@ -1,6 +1,7 @@ import Head from 'next/head'; import OgImage from '../../components/OgImage'; +import Table from '../../components/Table'; CldVideoPlayer Configuration - Next Cloudinary @@ -94,17 +95,6 @@ import OgImage from '../../components/OgImage'; Missing an option from the [Video Player docs](https://cloudinary.com/documentation/video_player_api_reference) you'd like to see? [Create an issue](https://github.com/cloudinary-community/next-cloudinary/issues/new?assignees=&labels=Type%3A+Feature&template=feature_request.md&title=%5BFeature%5D+)! -## Config & Delivery - -| Prop Name | Type | Default | Description | Example | -|--------------------|----------------|-----------------|------------------------------------------|------------------------------| -| cname | string | - | The custom domain name (CNAME) to use for building URLs, requires secure: false. | mycname.com | -| privateCdn | boolean | false | Set this parameter to true if you are an Advanced plan user with a private CDN distribution | `true` | -| secureDistribution | string | - | The custom domain name (CNAME) to use for building URLs, requires secure: true. | mycname.com | -| queryParams | string/object | - | Query parameters to append to video URL | `{myParam: 'value'}` | - -Missing an option from the [Video Player docs](https://cloudinary.com/documentation/video_player_api_reference) you'd like to see? [Create an issue](https://github.com/cloudinary-community/next-cloudinary/issues/new?assignees=&labels=Type%3A+Feature&template=feature_request.md&title=%5BFeature%5D+)! - ## Customization ### Logo @@ -180,4 +170,121 @@ To do this, create a new Ref instance and pass that in as the value of the prop: const myVideoRef = useRef(); ... +``` + +## Advanced + +### Configuration & Delivery + +
({`{ url: { cloudName: 'spacejelly' } }`}), + more: () => (More Info) + }, + { + prop: 'cname', + type: 'string', + example: () => ({`spacejelly.dev`}), + more: () => (More Info) + }, + { + prop: 'privateCdn', + type: 'boolean', + example: () => ({`true`}), + more: () => (More Info) + }, + { + prop: 'secureDistribution', + type: 'string', + example: () => ({`spacejelly.dev`}), + more: () => (More Info) + }, + { + prop: 'queryParams', + type: 'string/object', + example: () => ({`{myParam: 'value'}`}), + more: () => (More Info) + }, + ]} +/> + +#### `config` + +Allows configuration for the Cloudinary environment. + +**Examples** + +```jsx copy +config={{ + cloud: { + cloudName: '', + } +}} +``` + +#### `cname` + +The custom domain name (CNAME) to use for building URLs, requires secure: false. + +**Examples** + +```jsx copy +cname="spacejelly.dev" +``` + +#### `privateCdn` + +Set this parameter to true if you are an Advanced plan user with a private CDN distribution + +**Examples** + +```jsx copy +privateCdn={true} +``` + +#### `secureDistribution` + +The custom domain name (CNAME) to use for building URLs, requires secure: true. + +**Examples** + +```jsx copy +secureDistribution="spacejelly.dev" +``` + +#### `queryParams` + +Query parameters to append to video URL. + +**Examples** + +```jsx copy +queryParams={{ + myParam: 'value' +}} ``` \ No newline at end of file diff --git a/docs/pages/installation.mdx b/docs/pages/installation.mdx index 64f93a11..1cf39f9c 100644 --- a/docs/pages/installation.mdx +++ b/docs/pages/installation.mdx @@ -58,7 +58,32 @@ Don't have a Cloudinary account? " +CLOUDINARY_API_SECRET="" +``` + +The API Key is a publicly available value where the Secret must be kept private only to be used on the server. + +## Global Configuration + +Here are all of the available configurable environment variables for Next Cloudinary: + + + Note: These are not all required to use the library. + + +```ansi copy +NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME="" +NEXT_PUBLIC_CLOUDINARY_API_KEY="" +CLOUDINARY_API_SECRET="" + +NEXT_PUBLIC_CLOUDINARY_SECURE_DISTRIBUTION="" +NEXT_PUBLIC_CLOUDINARY_PRIVATE_CDN="" +``` ## Using Next Cloudinary diff --git a/next-cloudinary/src/components/CldUploadWidget/CldUploadWidget.tsx b/next-cloudinary/src/components/CldUploadWidget/CldUploadWidget.tsx index c833dd19..6ad9d8bb 100644 --- a/next-cloudinary/src/components/CldUploadWidget/CldUploadWidget.tsx +++ b/next-cloudinary/src/components/CldUploadWidget/CldUploadWidget.tsx @@ -19,7 +19,7 @@ import { CldUploadWidgetWidgetInstance, } from './CldUploadWidget.types'; -import {checkForCloudName} from "../../lib/cloudinary"; +import { getCloudinaryConfig } from "../../lib/cloudinary"; const WIDGET_WATCHED_EVENTS = [ 'success', @@ -43,6 +43,7 @@ const WIDGET_EVENTS: { [key: string]: string } = { const CldUploadWidget = ({ children, + config, onError, onOpen, onUpload, @@ -65,16 +66,16 @@ const CldUploadWidget = ({ // either on page load or during the upload process. Read more about signed uploads at: // https://cloudinary.com/documentation/upload_widget#signed_uploads + const cloudinaryConfig = getCloudinaryConfig(config); + const { cloudName, apiKey } = cloudinaryConfig?.cloud; + const uploadOptions = { - cloudName: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME, + cloudName, uploadPreset: uploadPreset || process.env.NEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET, - apiKey: process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY, + apiKey, ...options, }; - //Check if Cloud Name exists - checkForCloudName(process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME); - if ( signed ) { uploadOptions.uploadSignature = generateSignature; diff --git a/next-cloudinary/src/components/CldUploadWidget/CldUploadWidget.types.ts b/next-cloudinary/src/components/CldUploadWidget/CldUploadWidget.types.ts index 0bf6b493..e4290f35 100644 --- a/next-cloudinary/src/components/CldUploadWidget/CldUploadWidget.types.ts +++ b/next-cloudinary/src/components/CldUploadWidget/CldUploadWidget.types.ts @@ -4,12 +4,14 @@ import { CloudinaryUploadWidgetInstanceMethods, CloudinaryUploadWidgetError } from '@cloudinary-util/types'; +import { ConfigOptions } from "@cloudinary-util/url-loader"; export type CldUploadWidgetCloudinaryInstance = any; export type CldUploadWidgetWidgetInstance = any; export interface CldUploadWidgetProps { children?: ({ cloudinary, widget, open, results, error }: CldUploadWidgetPropsChildren) => JSX.Element; + config?: ConfigOptions; onError?: CldUploadEventCallbackError; onOpen?: CldUploadEventCallbackWidgetOnly; /** diff --git a/next-cloudinary/src/components/CldVideoPlayer/CldVideoPlayer.tsx b/next-cloudinary/src/components/CldVideoPlayer/CldVideoPlayer.tsx index 237aaf69..8b44b612 100644 --- a/next-cloudinary/src/components/CldVideoPlayer/CldVideoPlayer.tsx +++ b/next-cloudinary/src/components/CldVideoPlayer/CldVideoPlayer.tsx @@ -8,7 +8,7 @@ import { CldVideoPlayerProps } from './CldVideoPlayer.types'; import { getCldImageUrl } from '../../helpers/getCldImageUrl'; import { getCldVideoUrl } from '../../helpers/getCldVideoUrl'; -import {checkForCloudName} from "../../lib/cloudinary"; +import { getCloudinaryConfig } from "../../lib/cloudinary"; let playerInstances: string[] = []; @@ -20,6 +20,7 @@ const CldVideoPlayer = (props: CldVideoPlayerProps) => { autoplay, className, colors, + config, controls = true, fontFace, height, @@ -114,9 +115,6 @@ const CldVideoPlayer = (props: CldVideoPlayerProps) => { } } - //Check if Cloud Name exists - checkForCloudName(process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME); - /** * handleOnLoad * @description Stores the Cloudinary window instance to a ref when the widget script loads @@ -153,11 +151,17 @@ const CldVideoPlayer = (props: CldVideoPlayerProps) => { autoplayModeValue = autoplay; } + const cloudinaryConfig = getCloudinaryConfig(config); + const { cloudName } = cloudinaryConfig?.cloud; + const { secureDistribution, privateCdn } = cloudinaryConfig?.url; let playerOptions: CloudinaryVideoPlayerOptions = { + cloud_name: cloudName, + privateCdn, + secureDistribution, + autoplayMode: autoplayModeValue, autoplay: autoPlayValue, - cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME, controls, fontFace: fontFace || '', language, diff --git a/next-cloudinary/src/components/CldVideoPlayer/CldVideoPlayer.types.ts b/next-cloudinary/src/components/CldVideoPlayer/CldVideoPlayer.types.ts index 0131da06..0dd0b9b5 100644 --- a/next-cloudinary/src/components/CldVideoPlayer/CldVideoPlayer.types.ts +++ b/next-cloudinary/src/components/CldVideoPlayer/CldVideoPlayer.types.ts @@ -4,12 +4,14 @@ import { CloudinaryVideoPlayerOptionsLogo, CloudinaryVideoPlayerOptions } from '@cloudinary-util/types'; +import { ConfigOptions } from "@cloudinary-util/url-loader"; import { GetCldImageUrlOptions } from '../../helpers/getCldImageUrl'; import { GetCldVideoUrlOptions } from '../../helpers/getCldVideoUrl'; export type CldVideoPlayerProps = Omit & { className?: string; + config?: ConfigOptions; id?: string; logo?: boolean | CldVideoPlayerPropsLogo; onDataLoad?: Function; diff --git a/next-cloudinary/src/helpers/getCldImageUrl.ts b/next-cloudinary/src/helpers/getCldImageUrl.ts index 147335b5..6af3d44a 100644 --- a/next-cloudinary/src/helpers/getCldImageUrl.ts +++ b/next-cloudinary/src/helpers/getCldImageUrl.ts @@ -1,8 +1,7 @@ import { constructCloudinaryUrl } from '@cloudinary-util/url-loader'; import type { ImageOptions, ConfigOptions, AnalyticsOptions } from '@cloudinary-util/url-loader'; -import { NEXT_CLOUDINARY_ANALYTICS_PRODUCT_ID, NEXT_CLOUDINARY_ANALYTICS_ID, NEXT_CLOUDINARY_VERSION, NEXT_VERSION } from '../constants/analytics'; -import {checkForCloudName} from "../lib/cloudinary"; +import { getCloudinaryConfig, getCloudinaryAnalytics } from "../lib/cloudinary"; /** * getCldImageUrl @@ -13,22 +12,9 @@ export type GetCldImageUrlConfig = ConfigOptions; export type GetCldImageUrlAnalytics = AnalyticsOptions; export function getCldImageUrl(options: GetCldImageUrlOptions, config?: GetCldImageUrlConfig, analytics?: GetCldImageUrlAnalytics) { - // @ts-expect-error Property 'cloud' does not exist on type 'CloudinaryConfigurationOptions'. - const cloudName = config?.cloud?.cloudName ?? process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME; - checkForCloudName(cloudName); return constructCloudinaryUrl({ options, - config: Object.assign({ - cloud: { - cloudName: cloudName - }, - }, config), - analytics: Object.assign({ - product: NEXT_CLOUDINARY_ANALYTICS_PRODUCT_ID, - sdkCode: NEXT_CLOUDINARY_ANALYTICS_ID, - sdkSemver: NEXT_CLOUDINARY_VERSION, - techVersion: NEXT_VERSION, - feature: '' - }, analytics) + config: getCloudinaryConfig(config), + analytics: getCloudinaryAnalytics(analytics) }); } diff --git a/next-cloudinary/src/helpers/getCldVideoUrl.ts b/next-cloudinary/src/helpers/getCldVideoUrl.ts index 28abbbc6..b113b5d6 100644 --- a/next-cloudinary/src/helpers/getCldVideoUrl.ts +++ b/next-cloudinary/src/helpers/getCldVideoUrl.ts @@ -1,8 +1,7 @@ import { constructCloudinaryUrl } from '@cloudinary-util/url-loader'; import type { VideoOptions, ConfigOptions, AnalyticsOptions } from '@cloudinary-util/url-loader'; -import { NEXT_CLOUDINARY_ANALYTICS_PRODUCT_ID, NEXT_CLOUDINARY_ANALYTICS_ID, NEXT_CLOUDINARY_VERSION, NEXT_VERSION } from '../constants/analytics'; -import {checkForCloudName} from "../lib/cloudinary"; +import { getCloudinaryConfig, getCloudinaryAnalytics } from "../lib/cloudinary"; /** * getCldVideoUrl @@ -13,25 +12,12 @@ export type GetCldVideoUrlConfig = ConfigOptions; export type GetCldVideoUrlAnalytics = AnalyticsOptions; export function getCldVideoUrl(options: GetCldVideoUrlOptions, config?: GetCldVideoUrlConfig, analytics?: GetCldVideoUrlAnalytics) { - // @ts-expect-error Property 'cloud' does not exist on type 'CloudinaryConfigurationOptions'. - const cloudName = config?.cloud?.cloudName ?? process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME; - checkForCloudName(cloudName); return constructCloudinaryUrl({ options: { assetType: 'video', ...options }, - config: Object.assign({ - cloud: { - cloudName: cloudName - }, - }, config), - analytics: Object.assign({ - product: NEXT_CLOUDINARY_ANALYTICS_PRODUCT_ID, - sdkCode: NEXT_CLOUDINARY_ANALYTICS_ID, - sdkSemver: NEXT_CLOUDINARY_VERSION, - techVersion: NEXT_VERSION, - feature: '' - }, analytics) + config: getCloudinaryConfig(config), + analytics: getCloudinaryAnalytics(analytics) }); } diff --git a/next-cloudinary/src/lib/cloudinary.ts b/next-cloudinary/src/lib/cloudinary.ts index b7197d25..6ecad81d 100644 --- a/next-cloudinary/src/lib/cloudinary.ts +++ b/next-cloudinary/src/lib/cloudinary.ts @@ -1,3 +1,7 @@ +import { AnalyticsOptions, ConfigOptions } from "@cloudinary-util/url-loader"; + +import { NEXT_CLOUDINARY_ANALYTICS_PRODUCT_ID, NEXT_CLOUDINARY_ANALYTICS_ID, NEXT_CLOUDINARY_VERSION, NEXT_VERSION } from '../constants/analytics'; + /** * pollForProcessingImage */ @@ -30,8 +34,45 @@ export async function pollForProcessingImage(options: PollForProcessingImageOpti return true; } -export function checkForCloudName(cloudName: string | undefined) { +/** + * getCloudinaryConfig + */ + +export function getCloudinaryConfig(config?: ConfigOptions) { + const cloudName = config?.cloud?.cloudName ?? process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME; + if (!cloudName) { throw new Error('A Cloudinary Cloud name is required, please make sure NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME is set and configured in your environment.'); } + + const apiKey = config?.cloud?.apiKey ?? process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY; + const secureDistribution = config?.url?.secureDistribution ?? process.env.NEXT_PUBLIC_CLOUDINARY_SECURE_DISTRIBUTION; + const privateCdn = config?.url?.privateCdn ?? process.env.NEXT_PUBLIC_CLOUDINARY_PRIVATE_CDN; + + return Object.assign({ + cloud: { + ...config?.cloud, + apiKey, + cloudName + }, + url: { + ...config?.url, + secureDistribution, + privateCdn + } + }, config); +} + +/** + * getCloudinaryAnalytics + */ + +export function getCloudinaryAnalytics(analytics?: AnalyticsOptions) { + return Object.assign({ + product: NEXT_CLOUDINARY_ANALYTICS_PRODUCT_ID, + sdkCode: NEXT_CLOUDINARY_ANALYTICS_ID, + sdkSemver: NEXT_CLOUDINARY_VERSION, + techVersion: NEXT_VERSION, + feature: '' + }, analytics) } \ No newline at end of file diff --git a/next-cloudinary/tests/helpers/getCldImageUrl.spec.js b/next-cloudinary/tests/helpers/getCldImageUrl.spec.js index 9ec6b490..d4cc330b 100644 --- a/next-cloudinary/tests/helpers/getCldImageUrl.spec.js +++ b/next-cloudinary/tests/helpers/getCldImageUrl.spec.js @@ -29,4 +29,24 @@ describe('Cloudinary', () => { expect(url).toContain(`https://res.cloudinary.com/${cloudName}/image/upload/c_limit,w_100/f_auto/q_auto/turtle`); }); }); + + describe('Config', () => { + it('should configure a cname via secure distribution environment variables', () => { + const cloudName = 'customtestcloud'; + const secureDistrubtion = 'mywebsite.dev'; + const privateCdn = true; + + process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME = cloudName; + process.env.NEXT_PUBLIC_CLOUDINARY_SECURE_DISTRIBUTION = secureDistrubtion; + process.env.NEXT_PUBLIC_CLOUDINARY_PRIVATE_CDN = privateCdn; + + const url = getCldImageUrl({ + src: 'turtle', + width: 100, + height: 100 + }); + + expect(url).toContain(`https://${secureDistrubtion}/image/upload/c_limit,w_100/f_auto/q_auto/turtle`); + }); + }); }) diff --git a/next-cloudinary/tests/helpers/getCldVideoUrl.spec.js b/next-cloudinary/tests/helpers/getCldVideoUrl.spec.js index 9ee4fc90..b99fd8c7 100644 --- a/next-cloudinary/tests/helpers/getCldVideoUrl.spec.js +++ b/next-cloudinary/tests/helpers/getCldVideoUrl.spec.js @@ -29,4 +29,24 @@ describe('Cloudinary', () => { expect(url).toContain(`https://res.cloudinary.com/${cloudName}/video/upload/c_limit,w_100/f_auto/q_auto/turtle`); }); }); + + describe('Config', () => { + it('should configure a cname via secure distribution environment variables', () => { + const cloudName = 'customtestcloud'; + const secureDistrubtion = 'mywebsite.dev'; + const privateCdn = true; + + process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME = cloudName; + process.env.NEXT_PUBLIC_CLOUDINARY_SECURE_DISTRIBUTION = secureDistrubtion; + process.env.NEXT_PUBLIC_CLOUDINARY_PRIVATE_CDN = privateCdn; + + const url = getCldVideoUrl({ + src: 'turtle', + width: 100, + height: 100 + }); + + expect(url).toContain(`https://${secureDistrubtion}/video/upload/c_limit,w_100/f_auto/q_auto/turtle`); + }); + }); }) diff --git a/next-cloudinary/tests/loaders/cloudinary-loader.spec.js b/next-cloudinary/tests/loaders/cloudinary-loader.spec.js index e3b0261d..9182fce2 100644 --- a/next-cloudinary/tests/loaders/cloudinary-loader.spec.js +++ b/next-cloudinary/tests/loaders/cloudinary-loader.spec.js @@ -294,7 +294,6 @@ describe('Cloudinary Loader', () => { expect(result).toContain(`https://${config.url.secureDistribution}/${cldConfig.cloud.cloudName}/image/upload`) }); - }); }) });