Skip to content

Commit

Permalink
replaced URL with manual query parsing and added fixed ClientResponse…
Browse files Browse the repository at this point in the history
…Error wrapping
  • Loading branch information
ganigeorgiev committed Jun 7, 2023
1 parent ecc1c2e commit 04b699f
Show file tree
Hide file tree
Showing 19 changed files with 193 additions and 22 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 0.15.2

- Replaced `new URL(...)` with manual url parsing as it is not fully supported in React Native ([pocketbase#2484](https://github.com/pocketbase/pocketbase/discussions/2484#discussioncomment-6114540)).

- Fixed nested `ClientResponseErrors.originalError` wrapping and added `ClientResponseErrors` constructor tests.


## 0.15.1

- Cancel any pending subscriptions submit requests on realtime disconnect ([#204](https://github.com/pocketbase/js-sdk/issues/204)).
Expand Down
5 changes: 5 additions & 0 deletions dist/pocketbase.cjs.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,11 @@ declare class RecordService extends CrudService<Record> {
*/
unlinkExternalAuth(recordId: string, provider: string, queryParams?: BaseQueryParams): Promise<boolean>;
// ---------------------------------------------------------------
// very rudimentary url query params replacement because at the moment
// URL (and URLSearchParams) doesn't seem to be fully supported in React Native
//
// note: for details behind some of the decode/encode parsing check https://unixpapa.com/js/querystring.html
private _replaceQueryParams;
private _defaultUrlCallback;
}
declare class SchemaField {
Expand Down
2 changes: 1 addition & 1 deletion dist/pocketbase.cjs.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/pocketbase.cjs.js.map

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions dist/pocketbase.es.d.mts
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,11 @@ declare class RecordService extends CrudService<Record> {
*/
unlinkExternalAuth(recordId: string, provider: string, queryParams?: BaseQueryParams): Promise<boolean>;
// ---------------------------------------------------------------
// very rudimentary url query params replacement because at the moment
// URL (and URLSearchParams) doesn't seem to be fully supported in React Native
//
// note: for details behind some of the decode/encode parsing check https://unixpapa.com/js/querystring.html
private _replaceQueryParams;
private _defaultUrlCallback;
}
declare class SchemaField {
Expand Down
2 changes: 1 addition & 1 deletion dist/pocketbase.es.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/pocketbase.es.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/pocketbase.es.mjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/pocketbase.es.mjs.map

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions dist/pocketbase.iife.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,11 @@ declare class RecordService extends CrudService<Record> {
*/
unlinkExternalAuth(recordId: string, provider: string, queryParams?: BaseQueryParams): Promise<boolean>;
// ---------------------------------------------------------------
// very rudimentary url query params replacement because at the moment
// URL (and URLSearchParams) doesn't seem to be fully supported in React Native
//
// note: for details behind some of the decode/encode parsing check https://unixpapa.com/js/querystring.html
private _replaceQueryParams;
private _defaultUrlCallback;
}
declare class SchemaField {
Expand Down
2 changes: 1 addition & 1 deletion dist/pocketbase.iife.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/pocketbase.iife.js.map

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions dist/pocketbase.umd.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,11 @@ declare class RecordService extends CrudService<Record> {
*/
unlinkExternalAuth(recordId: string, provider: string, queryParams?: BaseQueryParams): Promise<boolean>;
// ---------------------------------------------------------------
// very rudimentary url query params replacement because at the moment
// URL (and URLSearchParams) doesn't seem to be fully supported in React Native
//
// note: for details behind some of the decode/encode parsing check https://unixpapa.com/js/querystring.html
private _replaceQueryParams;
private _defaultUrlCallback;
}
declare class SchemaField {
Expand Down
2 changes: 1 addition & 1 deletion dist/pocketbase.umd.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/pocketbase.umd.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "0.15.1",
"version": "0.15.2",
"name": "pocketbase",
"description": "PocketBase JavaScript SDK",
"author": "Gani Georgiev",
Expand Down
22 changes: 15 additions & 7 deletions src/ClientResponseError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,23 @@ export default class ClientResponseError extends Error {
// https://github.com/Microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
Object.setPrototypeOf(this, ClientResponseError.prototype);

if (!(errData instanceof ClientResponseError)) {
this.originalError = errData;
if (errData !== null && typeof errData === 'object') {
this.url = typeof errData.url === 'string' ? errData.url : '';
this.status = typeof errData.status === 'number' ? errData.status : 0;
this.isAbort = !!errData.isAbort;
this.originalError = errData.originalError;

if (errData.response !== null && typeof errData.response === 'object') {
this.response = errData.response;
} else if (errData.data !== null && typeof errData.data === 'object') {
this.response = errData.data;
} else {
this.response = {};
}
}

if (errData !== null && typeof errData === 'object') {
this.url = typeof errData.url === 'string' ? errData.url : '';
this.status = typeof errData.status === 'number' ? errData.status : 0;
this.response = errData.data !== null && typeof errData.data === 'object' ? errData.data : {};
this.isAbort = !!errData.isAbort;
if (!this.originalError && !(errData instanceof ClientResponseError)) {
this.originalError = errData;
}

if (typeof DOMException !== 'undefined' && errData instanceof DOMException) {
Expand Down
68 changes: 64 additions & 4 deletions src/services/RecordService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,13 +473,16 @@ export default class RecordService extends CrudService<Record> {
}
});

const url = new URL(provider.authUrl + redirectUrl);
url.searchParams.set("state", this.client.realtime.clientId);
const replacements: {[key: string]: any} = {
"state": this.client.realtime.clientId,
}
if (config.scopes?.length) {
url.searchParams.set("scope", config.scopes.join(" "));
replacements["scope"] = config.scopes.join(" ");
}

await (config.urlCallback ? config.urlCallback(url.toString()) : this._defaultUrlCallback(url.toString()));
const url = this._replaceQueryParams(provider.authUrl + redirectUrl, replacements);

await (config.urlCallback ? config.urlCallback(url) : this._defaultUrlCallback(url));
} catch (err) {
reject(new ClientResponseError(err));
}
Expand Down Expand Up @@ -659,6 +662,63 @@ export default class RecordService extends CrudService<Record> {

// ---------------------------------------------------------------

// very rudimentary url query params replacement because at the moment
// URL (and URLSearchParams) doesn't seem to be fully supported in React Native
//
// note: for details behind some of the decode/encode parsing check https://unixpapa.com/js/querystring.html
private _replaceQueryParams(url: string, replacements: {[key: string]: any} = {}): string {
let urlPath = url
let query = "";

const queryIndex = url.indexOf("?");
if (queryIndex >= 0) {
urlPath = url.substring(0, url.indexOf("?"));
query = url.substring(url.indexOf("?") + 1);
}

const parsedParams: {[key: string]: string} = {};

// parse the query parameters
const rawParams = query.split("&");
for (const param of rawParams) {
if (param == "") {
continue
}

const pair = param.split("=");
parsedParams[decodeURIComponent(pair[0].replace(/\+/g,' '))] = decodeURIComponent((pair[1] || "").replace(/\+/g,' '));
}

// apply the replacements
for (let key in replacements) {
if (!replacements.hasOwnProperty(key)) {
continue;
}

if (replacements[key] == null) {
delete parsedParams[key];
} else {
parsedParams[key] = replacements[key];
}
}

// construct back the full query string
query = "";
for (let key in parsedParams) {
if (!parsedParams.hasOwnProperty(key)) {
continue;
}

if (query != "") {
query += "&";
}

query += encodeURIComponent(key.replace(/%20/g,'+')) + "=" + encodeURIComponent(parsedParams[key].replace(/%20/g,'+'));
}

return query != "" ? (urlPath + "?" + query) : urlPath;
}

private _defaultUrlCallback(url: string) {
if (typeof window === "undefined" || !window?.open) {
throw new ClientResponseError(new Error(`Not in a browser context - please pass a custom urlCallback function.`));
Expand Down
76 changes: 76 additions & 0 deletions tests/ClientResponseError.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { assert } from 'chai';
import ClientResponseError from '@/ClientResponseError';

describe('ClientResponseError', function() {
describe('constructor()', function() {
it('with object-like value', function() {
const err = new ClientResponseError({
url: "http://example.com",
status: 400,
response: {message: "test message"},
isAbort: true,
originalError: "test"
});

assert.equal(err.url, "http://example.com");
assert.equal(err.status, 400);
assert.deepEqual(err.response, {message: "test message"});
assert.equal(err.isAbort, true);
assert.equal(err.originalError, "test");
assert.equal(err.message, "test message");
});

it('with non-object value', function() {
const err = new ClientResponseError("test");

assert.equal(err.url, "");
assert.equal(err.status, 0);
assert.deepEqual(err.response, {});
assert.equal(err.isAbort, false);
assert.equal(err.originalError, "test");
assert.equal(err.message, "Something went wrong while processing your request.");
});

it('with plain error', function() {
const plainErr = new Error("test")
const err = new ClientResponseError(plainErr);

assert.equal(err.url, "");
assert.equal(err.status, 0);
assert.deepEqual(err.response, {});
assert.equal(err.isAbort, false);
assert.equal(err.originalError, plainErr);
assert.equal(err.message, "Something went wrong while processing your request.");
});

it('with ClientResponseError error', function() {
const err0 = new ClientResponseError({
url: "http://example.com",
status: 400,
response: {message: "test message"},
isAbort: true,
originalError: "test"
});
const err = new ClientResponseError(err0);

assert.equal(err.url, "http://example.com");
assert.equal(err.status, 400);
assert.deepEqual(err.response, {message: "test message"});
assert.equal(err.isAbort, true);
assert.equal(err.originalError, "test");
assert.equal(err.message, "test message");
});

it('with abort error', function() {
const err0 = new DOMException("test");
const err = new ClientResponseError(err0);

assert.equal(err.url, "");
assert.equal(err.status, 0);
assert.deepEqual(err.response, {});
assert.equal(err.isAbort, true);
assert.equal(err.originalError, err0);
assert.include(err.message, "request was autocancelled");
});
});
});

0 comments on commit 04b699f

Please sign in to comment.