From b9f37e3a7d87ed823498a258de02f2a43104e287 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Thu, 12 Dec 2024 20:40:22 -0500 Subject: [PATCH 1/2] chore: localize fetch-json to avoid importing old CJS package - rewrite fetch-jsonp as ESM but only locally since this is only used for demo purposes --- angular.json | 1 - docs/getting-started/troubleshooting.md | 3 +- package.json | 1 - src/app/examples/grid-editor.component.ts | 31 ++++++-- src/app/examples/grid-remote.component.ts | 1 - src/app/examples/jsonp.ts | 89 +++++++++++++++++++++++ yarn.lock | 5 -- 7 files changed, 113 insertions(+), 18 deletions(-) create mode 100644 src/app/examples/jsonp.ts diff --git a/angular.json b/angular.json index 4ab6e3f91..85afb8f04 100644 --- a/angular.json +++ b/angular.json @@ -20,7 +20,6 @@ "tsConfig": "tsconfig.app.json", "allowedCommonJsDependencies": [ "@fnando/sparkline", - "fetch-jsonp", "stream" ], "assets": [ diff --git a/docs/getting-started/troubleshooting.md b/docs/getting-started/troubleshooting.md index d1ced98f4..73778450e 100644 --- a/docs/getting-started/troubleshooting.md +++ b/docs/getting-started/troubleshooting.md @@ -1,4 +1,4 @@ -## Troubleshooting +## Troubleshooting ### `ngcc` Build Warnings (Angular >=8.0 && <16.0) You might get warnings about SlickGrid while doing a production build, most of them are fine and the best way to fix them, is to simply remove/ignore the warnings, all you have to do is to add a file named `ngcc.config.js` in your project root (same location as the `angular.json` file) with the following content (you can also see this [commit](https://github.com/ghiscoding/angular-slickgrid-demos/commit/1fe8092bcd2e99ede5ab048f4a7ebe6254e4bee0) which fixes the Angular-Slickgrid-Demos prod build): @@ -44,7 +44,6 @@ This is no longer the case. Verify if you need this module and configure a polyf ```ts "options": { "allowedCommonJsDependencies": [ - "fetch-jsonp", "stream" ], }, diff --git a/package.json b/package.json index bd0e71979..d9ebc0f20 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,6 @@ "eslint": "^9.16.0", "eslint-plugin-cypress": "^4.1.0", "eslint-plugin-n": "^17.14.0", - "fetch-jsonp": "^1.3.0", "jest": "^29.7.0", "jest-extended": "^4.0.2", "jest-preset-angular": "^14.4.1", diff --git a/src/app/examples/grid-editor.component.ts b/src/app/examples/grid-editor.component.ts index 1d0c1739e..c4c8b1d30 100644 --- a/src/app/examples/grid-editor.component.ts +++ b/src/app/examples/grid-editor.component.ts @@ -1,8 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { TranslateService } from '@ngx-translate/core'; -import fetchJsonp from 'fetch-jsonp'; - import { AngularGridInstance, AutocompleterOption, @@ -27,6 +25,8 @@ import { CustomInputEditor } from './custom-inputEditor'; import { CustomInputFilter } from './custom-inputFilter'; import { Subject } from 'rxjs'; +import fetchJsonp from './jsonp.js'; + const NB_ITEMS = 100; const URL_SAMPLE_COLLECTION_DATA = 'assets/data/collection_100_numbers.json'; const URL_COUNTRIES_COLLECTION = 'assets/data/countries.json'; @@ -338,11 +338,26 @@ export class GridEditorComponent implements OnInit { /** with Angular Http, note this demo won't work because of CORS */ // this.http.get(`http://gd.geobytes.com/AutoCompleteCity?q=${searchText}`).subscribe(data => updateCallback(data)); - /** with JSONP AJAX will work locally but not on the GitHub demo because of CORS */ - fetchJsonp(`http://gd.geobytes.com/AutoCompleteCity?q=${searchText}`) - .then((response) => response.json()) - .then((json) => updateCallback(json)) - .catch((ex) => console.log('invalid JSONP response', ex)); + const clearFunction = (functionName: string) => { + delete (window as any)[functionName]; + } + const processJSONPResponse = (data: any) => { + updateCallback(data); + clearFunction('processJSONPResponse'); + } + + (window as any).processJSONPResponse = processJSONPResponse; + const script = document.createElement('script'); + script.src = `http://gd.geobytes.com/AutoCompleteCity?q=${searchText}&callback=processJSONPResponse` + document.getElementsByTagName('head')[0].appendChild(script); + + // fetchJsonp(`http://gd.geobytes.com/AutoCompleteCity?q=${searchText}`, { crossorigin: true }) + // .then((response) => { + + // return response.json() + // }) + // .then((json) => updateCallback(json)) + // .catch((ex) => console.log('invalid JSONP response', ex)); }, } as AutocompleterOption, }, @@ -358,7 +373,7 @@ export class GridEditorComponent implements OnInit { filterOptions: { minLength: 3, fetch: (searchText: string, updateCallback: (items: false | any[]) => void) => { - fetchJsonp(`http://gd.geobytes.com/AutoCompleteCity?q=${searchText}`) + fetchJsonp(`http://gd.geobytes.com/AutoCompleteCity?q=${searchText}`, { crossorigin: true }) .then((response) => response.json()) .then((json) => updateCallback(json)) .catch((ex) => console.log('invalid JSONP response', ex)); diff --git a/src/app/examples/grid-remote.component.ts b/src/app/examples/grid-remote.component.ts index 2bf79635a..94d0efe54 100644 --- a/src/app/examples/grid-remote.component.ts +++ b/src/app/examples/grid-remote.component.ts @@ -1,4 +1,3 @@ -// import fetchJsonp from 'fetch-jsonp'; // import 'slickgrid/slick.remotemodel'; // SlickGrid Remote Plugin import { Component, OnInit, OnDestroy } from '@angular/core'; diff --git a/src/app/examples/jsonp.ts b/src/app/examples/jsonp.ts new file mode 100644 index 000000000..bef99c12a --- /dev/null +++ b/src/app/examples/jsonp.ts @@ -0,0 +1,89 @@ +/* + * copied and rewritten as ESM (just a simple rewrite as ESM to avoid loading a CJS package) + * https://github.com/camsong/fetch-jsonp/blob/master/src/fetch-jsonp.js + */ + +interface JsonpOptions { + timeout: number; + jsonpCallback: string; + jsonpCallbackFunction: string; + charset: string; + nonce: string; + referrerPolicy: string; + crossorigin: boolean; +}; + +const defaultOptions = { + timeout: 5000, + jsonpCallback: 'callback', + jsonpCallbackFunction: null, +}; +const generateCallbackFunction = () => `jsonp_${Date.now()}_${Math.ceil(Math.random() * 100000)}`; +const clearFunction = (functionName: string) => delete (window as any)[functionName]; +const removeScript = (scriptId: string) => { + const script = document.getElementById(scriptId); + if (script) { + document.getElementsByTagName('head')[0].removeChild(script); + } +}; + +function fetchJsonp(_url: string, options: Partial = {}): Promise<{ ok: boolean; json: () => Promise; }> { + // to avoid param reassign + let url = _url; + const timeout = options.timeout || defaultOptions.timeout; + const jsonpCallback = options.jsonpCallback || defaultOptions.jsonpCallback; + let timeoutId: any; + + return new Promise((resolve, reject) => { + const callbackFunction = options.jsonpCallbackFunction || generateCallbackFunction(); + const scriptId = `${jsonpCallback}_${callbackFunction}`; + + (window as any)[callbackFunction] = (response: T) => { + // keep consistent with fetch API + resolve({ ok: true, json: () => Promise.resolve(response) }); + if (timeoutId) clearTimeout(timeoutId); + removeScript(scriptId); + clearFunction(callbackFunction); + }; + + // Check if the user set their own params, and if not add a ? to start a list of params + url += (url.indexOf('?') === -1) ? '?' : '&'; + + const jsonpScript = document.createElement('script'); + jsonpScript.setAttribute('src', `${url}${jsonpCallback}=${callbackFunction}`); + if (options.charset) { + jsonpScript.setAttribute('charset', options.charset); + } + if (options.nonce) { + jsonpScript.setAttribute('nonce', options.nonce); + } + if (options.referrerPolicy) { + jsonpScript.setAttribute('referrerPolicy', options.referrerPolicy); + } + if (options.crossorigin) { + jsonpScript.setAttribute('crossorigin', 'true'); + } + jsonpScript.id = scriptId; + document.getElementsByTagName('head')[0].appendChild(jsonpScript); + + timeoutId = setTimeout(() => { + reject(new Error(`JSONP request to ${_url} timed out`)); + + clearFunction(callbackFunction); + removeScript(scriptId); + (window as any)[callbackFunction] = () => { + clearFunction(callbackFunction); + }; + }, timeout); + + // Caught if got 404/500 + jsonpScript.onerror = () => { + reject(new Error(`JSONP request to ${_url} failed`)); + clearFunction(callbackFunction); + removeScript(scriptId); + if (timeoutId) clearTimeout(timeoutId); + }; + }); +} + +export default fetchJsonp; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index bf0a5b82b..6eb6a7047 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6017,11 +6017,6 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -fetch-jsonp@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/fetch-jsonp/-/fetch-jsonp-1.3.0.tgz#99b8c25bd100938d7a7a6b5db9d6b81f32a10809" - integrity sha512-hxCYGvmANEmpkHpeWY8Kawfa5Z1t2csTpIClIDG/0S92eALWHRU1RnGaj86Tf5Cc0QF+afSa4SQ4pFB2rFM5QA== - fflate@^0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" From b1590306ca79c8dca5568d657ed8c4fce03ea136 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Thu, 12 Dec 2024 20:50:56 -0500 Subject: [PATCH 2/2] chore: fix build --- src/app/examples/grid-editor.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/examples/grid-editor.component.ts b/src/app/examples/grid-editor.component.ts index c4c8b1d30..9af50b13e 100644 --- a/src/app/examples/grid-editor.component.ts +++ b/src/app/examples/grid-editor.component.ts @@ -25,7 +25,7 @@ import { CustomInputEditor } from './custom-inputEditor'; import { CustomInputFilter } from './custom-inputFilter'; import { Subject } from 'rxjs'; -import fetchJsonp from './jsonp.js'; +import fetchJsonp from './jsonp'; const NB_ITEMS = 100; const URL_SAMPLE_COLLECTION_DATA = 'assets/data/collection_100_numbers.json';