diff --git a/.changeset/lucky-wolves-nail.md b/.changeset/lucky-wolves-nail.md new file mode 100644 index 00000000..c66a2188 --- /dev/null +++ b/.changeset/lucky-wolves-nail.md @@ -0,0 +1,5 @@ +--- +"@theoplayer/nielsen-connector-web": minor +--- + +Updated to be compatible with THEOplayer `6.X`. diff --git a/.gitignore b/.gitignore index c2658d7d..c28de259 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ node_modules/ + +# Generated by MacOS +.DS_Store diff --git a/.idea/web-connectors.iml b/.idea/web-connectors.iml index 23928731..4e0bc000 100644 --- a/.idea/web-connectors.iml +++ b/.idea/web-connectors.iml @@ -7,6 +7,7 @@ + diff --git a/conviva/BUILD.md b/conviva/BUILD.md deleted file mode 100644 index b75f4716..00000000 --- a/conviva/BUILD.md +++ /dev/null @@ -1,59 +0,0 @@ -# conviva-connector-web - -A connector implementing Conviva for web. - -## Getting started - -``` -npm install -``` - -## Testing and code quality - -A test stack is set up and can be used by adding tests to the `test/unit/` folder. Run these tests with - -``` -npm run test -``` - -This project is set up with [ESLint](https://eslint.org/) and [Prettier](https://prettier.io/). You can run these checks with - -``` -npm run lint -npm run prettier -``` - -but it's a good idea to set up the necessary IDE integration for both. - -CI will automatically verify whether the code passes all necessary quality gates. - -## Manual testing - -- Run `npm run serve` to start `http-server` in the root folder by running. -- Run `npm run build` to create the integrations library `conviva-connector.umd.js` under `dist/`. -- Navigate to `localhost:8080/test/pages/main_umd.html` or add an alternative test page. - -## Release process - -This release process is based on the assumption that the `master` branch is the default branch, and working branches are branched off from it and PR's back to it. -It is mostly automated by creating tags with a specific format on the `master` branch. - -### Prerequisites - -- All the necesary code for the release is present on the `master` branch. -- The `README.md` file contains the necessary information for an external developer to use the connector. This will be published along with the code. -- Bitbucket Pipelines has been correctly enabled for the repository. - - Github Actions integration is on the roadmap. -- The necessary variables have been configured for Bitbucket Pipelines: - - `NPMRC_CONTENT`: the full content of an `.npmrc` file to be used during publishing. Linebreaks in the file can be substituted by `\n` in the variable. - - `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`: The AWS credentials necessary to publish a bundle to CDN. - - This might be replaced with a Github release artifact later. - -### Creating a release - -These steps assume that the new release version is `X.Y.Z`. - -- Create a new version bump commit on the `master` branch to bump the project version to the release version. -- Create a tag on the version bump commit of the format `vX.Y.Z`. -- Verify that the release pipeline correctly runs for your new tag. -- Verify that the correct artifacts have been published once the pipeline has completed. diff --git a/conviva/LICENSE b/conviva/LICENSE.md similarity index 100% rename from conviva/LICENSE rename to conviva/LICENSE.md diff --git a/conviva/package.json b/conviva/package.json index 622067df..c257b7de 100644 --- a/conviva/package.json +++ b/conviva/package.json @@ -30,6 +30,7 @@ "dist/", "CHANGELOG.md", "README.md", + "LICENSE.md", "package.json" ], "dependencies": { diff --git a/nielsen/.gitignore b/nielsen/.gitignore new file mode 100644 index 00000000..92b3d00f --- /dev/null +++ b/nielsen/.gitignore @@ -0,0 +1,10 @@ +# Node artifact files +node_modules/ +lib/ +dist/ + +# Generated by MacOS +.DS_Store + +# Generated by Windows +Thumbs.db diff --git a/nielsen/CHANGELOG.md b/nielsen/CHANGELOG.md new file mode 100644 index 00000000..219f3188 --- /dev/null +++ b/nielsen/CHANGELOG.md @@ -0,0 +1,7 @@ +# @theoplayer/nielsen-connector-web + +## 1.0.0 + +### ✨ Features + +- Initial release diff --git a/yospace/LICENSE b/nielsen/LICENSE.md similarity index 100% rename from yospace/LICENSE rename to nielsen/LICENSE.md diff --git a/nielsen/README.md b/nielsen/README.md new file mode 100644 index 00000000..d598a24f --- /dev/null +++ b/nielsen/README.md @@ -0,0 +1,50 @@ +# Nielsen Web Connector + +A connector implementing Nielsen with THEOplayer. + +## Installation + +```sh +npm install @theoplayer/nielsen-connector-web +``` + +## Usage + +### Configuring the connector + +Create the connector by providing the `THEOplayer` instance, the Nielsen App ID, the channelName for the asset +and optionally some Nielsen configuration. + +```js +import {NielsenConnector} from "../../dist/THEOplayerNielsenConnector"; + +const appId = ''; +const channelName = ''; +// Optional +const options: NielsenOptions = { + containerId: 'THEOplayer', + optout: false +} +const nielsenConnector = new NielsenConnector(player, appId, channelName, options); +``` + +The `NielsenOptions` can have the following fields: + +| Key | Value | +|-----------------|-----------------------------------------------------------------| +| ` containerId ` | HTML DOM element id of the player container. | +| ` nol_sdkDebug` | Enables Debug Mode which allows output to be viewed in console. | +| ` optout ` | Whether to opt-out of Nielsen Measurement. | + +### Passing metadata dynamically + +The connector allows updating the current asset's metadata at any time: + +```js +const metadata = { + ['channelName']: 'newChannelName', + ['customTag1']: 'customValue1', + ['customTag2']: 'customValue2' +} +nielsenConnector.updateMetadata(metadata); +``` diff --git a/nielsen/jest.config.js b/nielsen/jest.config.js new file mode 100644 index 00000000..78e93fe8 --- /dev/null +++ b/nielsen/jest.config.js @@ -0,0 +1,4 @@ +module.exports = { + preset: "ts-jest", + testEnvironment: "node" +}; diff --git a/nielsen/package.json b/nielsen/package.json new file mode 100644 index 00000000..c7e12c08 --- /dev/null +++ b/nielsen/package.json @@ -0,0 +1,37 @@ +{ + "name": "@theoplayer/nielsen-connector-web", + "version": "1.0.0", + "description": "A connector implementing Nielsen with THEOplayer.", + "main": "dist/THEOplayerNielsenConnector.umd.js", + "module": "dist/THEOplayerNielsenConnector.esm.js", + "types": "dist/THEOplayerNielsenConnector.d.ts", + "exports": { + ".": { + "types": "./dist/THEOplayerNielsenConnector.d.ts", + "import": "./dist/THEOplayerNielsenConnector.esm.js", + "require": "./dist/THEOplayerNielsenConnector.umd.js" + }, + "./dist/*": "./dist/*", + "./package": "./package.json", + "./package.json": "./package.json" + }, + "scripts": { + "clean": "rimraf lib dist", + "bundle": "rollup -c rollup.config.mjs", + "build": "npm run clean && npm run bundle", + "serve": "http-server", + "test": "jest" + }, + "author": "THEO Technologies NV", + "license": "MIT", + "files": [ + "dist/", + "CHANGELOG.md", + "README.md", + "LICENSE.md", + "package.json" + ], + "peerDependencies": { + "theoplayer": "^5.0.0 || ^6.0.0" + } +} diff --git a/nielsen/rollup.config.mjs b/nielsen/rollup.config.mjs new file mode 100644 index 00000000..30a21542 --- /dev/null +++ b/nielsen/rollup.config.mjs @@ -0,0 +1,14 @@ +import {getSharedBuildConfiguration} from "../tools/build.mjs"; +import fs from "node:fs"; + +const {version} = JSON.parse(fs.readFileSync("./package.json", "utf8")); + +const fileName = "nielsen-connector"; +const globalName = "THEOplayerNielsenConnector"; + +const banner = ` +/** + * Nielsen Web Connector v${version} + */`.trim(); + +export default getSharedBuildConfiguration(fileName, globalName, banner); diff --git a/nielsen/src/index.ts b/nielsen/src/index.ts new file mode 100644 index 00000000..ee3d9c68 --- /dev/null +++ b/nielsen/src/index.ts @@ -0,0 +1,2 @@ +export { NielsenConnector } from './integration/NielsenConnector'; +export { NielsenOptions } from './nielsen/Types'; diff --git a/nielsen/src/integration/NielsenConnector.ts b/nielsen/src/integration/NielsenConnector.ts new file mode 100644 index 00000000..aae4305b --- /dev/null +++ b/nielsen/src/integration/NielsenConnector.ts @@ -0,0 +1,27 @@ +import { ChromelessPlayer } from 'theoplayer'; +import { NielsenOptions } from '../nielsen/Types'; +import { NielsenHandler } from './NielsenHandler'; + +export class NielsenConnector { + private nielsenHandler: NielsenHandler; + + /** + * Create NielsenConnector + * + * @param player THEOplayer instance. + * @param appId UniqueID assigned to player/site. + * @param instanceName User-defined string value for describing the player/site. + * @param options Additional options. + */ + constructor(player: ChromelessPlayer, appId: string, instanceName: string, options?: NielsenOptions) { + this.nielsenHandler = new NielsenHandler(player, appId, instanceName, options); + } + + updateMetadata(metadata: { [key: string]: string }): void { + this.nielsenHandler.updateMetadata(metadata); + } + + destroy() { + this.nielsenHandler.destroy(); + } +} diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts new file mode 100644 index 00000000..fc544919 --- /dev/null +++ b/nielsen/src/integration/NielsenHandler.ts @@ -0,0 +1,151 @@ +import { Ad, AddTrackEvent, ChromelessPlayer, TextTrack, TextTrackEnterCueEvent, VolumeChangeEvent } from 'theoplayer'; +import { loadNielsenLibrary } from '../nielsen/NOLBUNDLE'; +import { AdMetadata, ContentMetadata, NielsenOptions } from '../nielsen/Types'; +import { getAdType } from '../utils/Util'; + +export class NielsenHandler { + private player: ChromelessPlayer; + + private nSdkInstance: any; + + private sessionInProgress: boolean = false; + + private duration: number = NaN; + + constructor(player: ChromelessPlayer, appId: string, instanceName: string, options?: NielsenOptions) { + this.player = player; + this.nSdkInstance = loadNielsenLibrary(appId, instanceName, options); + + this.addEventListeners(); + } + + updateMetadata(metadata: { [key: string]: string }): void { + this.nSdkInstance.ggPM('updateMetadata', metadata); + } + + private addEventListeners(): void { + this.player.addEventListener('play', this.onPlay); + this.player.addEventListener('ended', this.onEnd); + this.player.addEventListener('sourcechange', this.onSourceChange); + this.player.addEventListener('volumechange', this.onVolumeChange); + this.player.addEventListener('loadedmetadata', this.onLoadMetadata); + this.player.addEventListener('durationchange', this.onDurationChange); + + this.player.textTracks.addEventListener('addtrack', this.onAddTrack); + + if (this.player.ads) { + this.player.ads.addEventListener('adbegin', this.onAdBegin); + } + + window.addEventListener('beforeunload', this.onEnd); + } + + private removeEventListeners(): void { + this.player.removeEventListener('play', this.onPlay); + this.player.removeEventListener('ended', this.onEnd); + this.player.removeEventListener('sourcechange', this.onSourceChange); + this.player.removeEventListener('volumechange', this.onVolumeChange); + this.player.removeEventListener('loadedmetadata', this.onLoadMetadata); + this.player.removeEventListener('durationchange', this.onDurationChange); + + this.player.textTracks.removeEventListener('addtrack', this.onAddTrack); + + if (this.player.ads) { + this.player.ads.removeEventListener('adbegin', this.onAdBegin); + } + + window.removeEventListener('beforeunload', this.onEnd); + } + + private onPlay = () => { + this.maybeSendPlayEvent(); + }; + + private onEnd = () => { + this.endSession(); + }; + + private onSourceChange = () => { + this.duration = NaN; + this.endSession(); + }; + + private onVolumeChange = (event: VolumeChangeEvent) => { + const volumeLevel = this.player.muted ? 0 : event.volume * 100; + this.nSdkInstance.ggPM('setVolume', volumeLevel); + }; + + private onDurationChange = () => { + this.duration = this.player.duration; + this.maybeSendPlayEvent(); + }; + + private onLoadMetadata = () => { + const data: ContentMetadata = { + type: 'content', + adModel: '1' // Always '1' for DTVR + }; + this.nSdkInstance.ggPM('loadMetadata', data); + }; + + private onAddTrack = (event: AddTrackEvent) => { + if (event.track.kind === 'metadata') { + const track = event.track as TextTrack; + if (track.type === 'id3') { + // || track.type === 'emsg') { + if (track.mode === 'disabled') { + track.mode = 'hidden'; + } + track.addEventListener('entercue', this.onEnterCue); + } + } + }; + + private onEnterCue = (event: TextTrackEnterCueEvent) => { + const { cue } = event; + if (cue.track.type === 'id3') { + if (cue.content.id === 'PRIV' && cue.content.ownerIdentifier.indexOf('www.nielsen.com') !== -1) { + this.nSdkInstance.ggPM('sendID3', cue.content.ownerIdentifier); + } + } else { + // TODO emsg is not supported for now + } + }; + + private onAdBegin = () => { + const currentAd = this.player.ads!.currentAds.filter((ad: Ad) => ad.type === 'linear'); + const type = getAdType(this.player.ads!.currentAdBreak!); + const adMetadata: AdMetadata = { + type, + assetid: currentAd[0].id! + }; + this.nSdkInstance.ggPM('loadMetadata', adMetadata); + }; + + private maybeSendPlayEvent(): void { + if (!this.sessionInProgress && !Number.isNaN(this.duration)) { + this.sessionInProgress = true; + const metadataObject = { + channelName: this.player.src, + length: this.duration + }; + this.nSdkInstance.ggPM('play', metadataObject); + } + } + + private endSession(): void { + if (this.sessionInProgress) { + this.sessionInProgress = false; + this.nSdkInstance.ggPM('end', this.getPlayHeadPosition()); + } + } + + private getPlayHeadPosition(): string { + return Math.floor(this.player.currentTime).toString(); + } + + destroy() { + this.removeEventListeners(); + this.endSession(); + } +} diff --git a/nielsen/src/nielsen/NOLBUNDLE.ts b/nielsen/src/nielsen/NOLBUNDLE.ts new file mode 100644 index 00000000..87d67c1a --- /dev/null +++ b/nielsen/src/nielsen/NOLBUNDLE.ts @@ -0,0 +1,44 @@ +/* eslint-disable */ +import { NielsenOptions } from './Types'; + +export function loadNielsenLibrary(appId: string, instanceName: string, options?: NielsenOptions) { + // https://engineeringportal.nielsen.com/docs/DTVR_Browser_SDK + // Add Static Queue Snippet to initialize a Nielsen SDK Instance. + // @ts-ignore + !(function (t: Window, n: any) { + t[n] = t[n] || { + nlsQ: function (e: any, o: any, c?: any, r?: any, s?: any, i?: any) { + // @ts-ignore + return ( + (s = t.document), + (r = s.createElement('script')), + (r.async = 1), + (r.src = + ('http:' === t.location.protocol ? 'http:' : 'https:') + + '//cdn-gl.imrworldwide.com/conf/' + + e + + '.js#name=' + + o + + '&ns=' + + n), + (i = s.getElementsByTagName('script')[0]), + i.parentNode.insertBefore(r, i), + (t[n][o] = t[n][o] || { + g: c || {}, + ggPM: function (e: any, c: any, r?: any, s?: any, i?: any) { + // @ts-ignore + (t[n][o].q = t[n][o].q || []).push([e, c, r, s, i]); + } + }), + t[n][o] + ); + } + }; + })(window, 'NOLBUNDLE'); + + if (options) { + return (window as any).NOLBUNDLE.nlsQ(appId, instanceName, options); + } else { + return (window as any).NOLBUNDLE.nlsQ(appId, instanceName); + } +} diff --git a/nielsen/src/nielsen/Types.ts b/nielsen/src/nielsen/Types.ts new file mode 100644 index 00000000..f2ffe88a --- /dev/null +++ b/nielsen/src/nielsen/Types.ts @@ -0,0 +1,25 @@ +export type AdType = 'preroll' | 'midroll' | 'postroll' | 'ad'; + +export type NielsenOptions = { + // HTML DOM element id of the player container + containerId?: string; + + // Enables Debug Mode which allows output to be viewed in console. + nol_sdkDebug?: string; + + // Set the ability to optout on initialization of the SDK + optout?: boolean; +}; + +/** + * adModel: 1) - Linear – matches TV ad load * 2) Dynamic – Dynamic Ad Insertion (DAI) + */ +export type ContentMetadata = { + type: 'content'; + adModel: '1' | '2'; +} & { [key: string]: string }; + +export type AdMetadata = { + type: AdType; + assetid: any; // TODO string? or can be anything? +} & { [key: string]: string }; diff --git a/nielsen/src/utils/Util.ts b/nielsen/src/utils/Util.ts new file mode 100644 index 00000000..f3b89d0d --- /dev/null +++ b/nielsen/src/utils/Util.ts @@ -0,0 +1,15 @@ +import { AdBreak } from 'theoplayer'; +import { AdType } from '../nielsen/Types'; + +export function getAdType(adBreak: AdBreak): AdType { + const currentAdBreakTimeOffset = adBreak.timeOffset; + let currentAdBreakPosition: AdType = 'ad'; + if (currentAdBreakTimeOffset === 0) { + currentAdBreakPosition = 'preroll'; + } else if (currentAdBreakTimeOffset < 0) { + currentAdBreakPosition = 'postroll'; + } else if (currentAdBreakTimeOffset > 0) { + currentAdBreakPosition = 'midroll'; + } + return currentAdBreakPosition; +} diff --git a/nielsen/test/pages/main.html b/nielsen/test/pages/main.html new file mode 100644 index 00000000..7e9b6f65 --- /dev/null +++ b/nielsen/test/pages/main.html @@ -0,0 +1,43 @@ + + + + + Connector test page + + + + + +
+ + + diff --git a/nielsen/test/unit/example.spec.ts b/nielsen/test/unit/example.spec.ts new file mode 100644 index 00000000..6ddb9eac --- /dev/null +++ b/nielsen/test/unit/example.spec.ts @@ -0,0 +1,6 @@ +describe('My connector code', () => { + it('should be decently tested', () => { + const a: number = 3; + expect(a).toBe(3); + }); +}); diff --git a/nielsen/tsconfig.json b/nielsen/tsconfig.json new file mode 100644 index 00000000..5fd53772 --- /dev/null +++ b/nielsen/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "strict": true, + "baseUrl": "./", + "rootDir": ".", + "paths": { "THEOplayer": ["./src/THEOplayer"] }, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + } +} diff --git a/package-lock.json b/package-lock.json index 6f1f0d5b..59808251 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "license": "MIT", "workspaces": [ "yospace", - "conviva" + "conviva", + "nielsen" ], "devDependencies": { "@changesets/cli": "^2.27.1", @@ -42,13 +43,13 @@ }, "conviva": { "name": "@theoplayer/conviva-connector-web", - "version": "1.3.0", + "version": "2.0.0", "license": "MIT", "dependencies": { "@convivainc/conviva-js-coresdk": "^4.6.1" }, "peerDependencies": { - "@theoplayer/yospace-connector-web": "^2.0.0", + "@theoplayer/yospace-connector-web": "^2.1.0", "theoplayer": "^5.0.0 || ^6.0.0" }, "peerDependenciesMeta": { @@ -57,6 +58,20 @@ } } }, + "nielsen": { + "name": "@theoplayer/nielsen-connector-web", + "version": "1.0.0", + "license": "MIT", + "peerDependencies": { + "theoplayer": "^5.0.0 || ^6.0.0" + } + }, + "nielsen/node_modules/theoplayer": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/theoplayer/-/theoplayer-5.11.0.tgz", + "integrity": "sha512-Ywn3nnTPiyh0cckO3FGIvXfhr3BR16D967voMthnQlwY5jITEJq6pKYAt9qHEXyWNafULKVhY0X///61Wxm7lw==", + "peer": true + }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", @@ -2526,6 +2541,10 @@ "resolved": "conviva", "link": true }, + "node_modules/@theoplayer/nielsen-connector-web": { + "resolved": "nielsen", + "link": true + }, "node_modules/@theoplayer/yospace-connector-web": { "resolved": "yospace", "link": true @@ -9401,7 +9420,7 @@ }, "yospace": { "name": "@theoplayer/yospace-connector-web", - "version": "2.0.0", + "version": "2.1.0", "license": "MIT", "peerDependencies": { "theoplayer": "^5.0.0 || ^6.0.0" diff --git a/package.json b/package.json index 5fa7b0ba..12d23c2f 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "license": "MIT", "workspaces": [ "yospace", - "conviva" + "conviva", + "nielsen" ], "scripts": { "changeset:version": "changeset version && node .changeset/post-process.js", diff --git a/yospace/BUILD.md b/yospace/BUILD.md deleted file mode 100644 index d3319306..00000000 --- a/yospace/BUILD.md +++ /dev/null @@ -1,57 +0,0 @@ -# yospace-connector-web - -A connector implementing Yospace for web. - -## Getting started - -- `npm install` - -## Testing and code quality - -A test stack is set up and can be used by adding tests to the `test/unit/` folder. Run these tests with - -``` -npm run test -``` - -This project is set up with [ESLint](https://eslint.org/) and [Prettier](https://prettier.io/). You can run these checks with - -``` -npm run lint -npm run prettier -``` - -but it's a good idea to set up the necessary IDE integration for both. - -CI will automatically verify whether the code passes all necessary quality gates. - -## Manual testing - -- Run `npm run serve` to start `http-server` in the root folder by running. -- Run `npm run build` to create the integrations library `yospace-connector.umd.js` under `dist/`. -- Navigate to `localhost:8080/test/pages/main.html` or add an alternative test page. - -## Release process - -This release process is based on the assumption that the `master` branch is the default branch, and working branches are branched off from it and PR's back to it. -It is mostly automated by creating tags with a specific format on the `master` branch. - -### Prerequisites - -- All the necesary code for the release is present on the `master` branch. -- The `README.md` file contains the necessary information for an external developer to use the connector. This will be published along with the code. -- Bitbucket Pipelines has been correctly enabled for the repository. - - Github Actions integration is on the roadmap. -- The necessary variables have been configured for Bitbucket Pipelines: - - `NPMRC_CONTENT`: the full content of an `.npmrc` file to be used during publishing. Linebreaks in the file can be substituted by `\n` in the variable. - - `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`: The AWS credentials necessary to publish a bundle to CDN. - - This might be replaced with a Github release artifact later. - -### Creating a release - -These steps assume that the new release version is `X.Y.Z`. - -- Create a new version bump commit on the `master` branch to bump the project version to the release version. -- Create a tag on the version bump commit of the format `vX.Y.Z`. -- Verify that the release pipeline correctly runs for your new tag. -- Verify that the correct artifacts have been published once the pipeline has completed. diff --git a/yospace/LICENSE.md b/yospace/LICENSE.md new file mode 100644 index 00000000..146db44c --- /dev/null +++ b/yospace/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 THEO Technologies NV + +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. diff --git a/yospace/package.json b/yospace/package.json index 45661e43..6205e117 100644 --- a/yospace/package.json +++ b/yospace/package.json @@ -32,7 +32,9 @@ "license": "MIT", "files": [ "dist/", + "CHANGELOG.md", "README.md", + "LICENSE.md", "package.json" ], "peerDependencies": {