sharetribe-flex-sdk 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/.circleci/config.yml +22 -0
  2. package/.eslintignore +3 -0
  3. package/.eslintrc.js +19 -0
  4. package/CHANGELOG.md +231 -0
  5. package/LICENSE +201 -0
  6. package/README.md +76 -0
  7. package/build/sharetribe-flex-sdk-node.js +13592 -0
  8. package/build/sharetribe-flex-sdk-web.js +1 -0
  9. package/docs/README.md +20 -0
  10. package/docs/authentication.md +179 -0
  11. package/docs/calling-the-api.md +217 -0
  12. package/docs/configurations.md +95 -0
  13. package/docs/developing-sdk.md +131 -0
  14. package/docs/docpress.json +14 -0
  15. package/docs/features.md +14 -0
  16. package/docs/keep-alive.md +48 -0
  17. package/docs/object-query-parameters.md +36 -0
  18. package/docs/scripts.js +41 -0
  19. package/docs/serializing-types-to-json.md +40 -0
  20. package/docs/sharing-session-between-client-and-server.md +19 -0
  21. package/docs/styles.css +95 -0
  22. package/docs/token-store.md +114 -0
  23. package/docs/try-it-in-browser.md +32 -0
  24. package/docs/try-it-in-the-playground.md +153 -0
  25. package/docs/types.md +27 -0
  26. package/docs/writing-your-own-token-store.md +29 -0
  27. package/docs/your-own-types.md +61 -0
  28. package/examples/README.md +5 -0
  29. package/examples/getting-started-browser/README.md +22 -0
  30. package/examples/getting-started-browser/index.html +89 -0
  31. package/examples/getting-started-browser/index.js +156 -0
  32. package/examples/getting-started-browser/screenshots/screenshot1.png +0 -0
  33. package/examples/getting-started-browser/screenshots/screenshot2.png +0 -0
  34. package/examples/getting-started-node/README.md +23 -0
  35. package/examples/getting-started-node/index.js +139 -0
  36. package/examples/getting-started-node/screenshots/screenshot.png +0 -0
  37. package/package.json +83 -0
  38. package/playground.js +295 -0
  39. package/src/browser_cookie_store.js +26 -0
  40. package/src/context_runner.js +151 -0
  41. package/src/context_runner.test.js +185 -0
  42. package/src/detect.js +11 -0
  43. package/src/express_cookie_store.js +57 -0
  44. package/src/fake/adapter.js +130 -0
  45. package/src/fake/api.js +137 -0
  46. package/src/fake/auth.js +84 -0
  47. package/src/fake/token_store.js +231 -0
  48. package/src/index.js +25 -0
  49. package/src/interceptors/.eslintrc.js +5 -0
  50. package/src/interceptors/add_auth_header.js +32 -0
  51. package/src/interceptors/add_auth_header.test.js +50 -0
  52. package/src/interceptors/add_auth_token_response.js +16 -0
  53. package/src/interceptors/add_client_id_to_params.js +12 -0
  54. package/src/interceptors/add_client_secret_to_params.js +15 -0
  55. package/src/interceptors/add_grant_type_to_params.js +23 -0
  56. package/src/interceptors/add_idp_client_id_to_params.js +17 -0
  57. package/src/interceptors/add_idp_id_to_params.js +17 -0
  58. package/src/interceptors/add_idp_token_to_params.js +17 -0
  59. package/src/interceptors/add_scope_to_params.js +18 -0
  60. package/src/interceptors/add_subject_token_to_params.js +18 -0
  61. package/src/interceptors/add_token_exchange_grant_type_to_params.js +12 -0
  62. package/src/interceptors/auth_info.js +50 -0
  63. package/src/interceptors/clear_token_after_revoke.js +45 -0
  64. package/src/interceptors/default_params.js +12 -0
  65. package/src/interceptors/fetch_auth_token_from_api.js +33 -0
  66. package/src/interceptors/fetch_auth_token_from_store.js +27 -0
  67. package/src/interceptors/fetch_refresh_token_for_revoke.js +24 -0
  68. package/src/interceptors/multipart_request.js +35 -0
  69. package/src/interceptors/retry_with_anon_token.js +58 -0
  70. package/src/interceptors/retry_with_refresh_token.js +70 -0
  71. package/src/interceptors/save_token.js +20 -0
  72. package/src/interceptors/transit_request.js +27 -0
  73. package/src/interceptors/transit_request.test.js +58 -0
  74. package/src/interceptors/transit_response.js +27 -0
  75. package/src/memory_store.js +19 -0
  76. package/src/params_serializer.js +65 -0
  77. package/src/params_serializer.test.js +58 -0
  78. package/src/sdk.js +894 -0
  79. package/src/sdk.test.js +908 -0
  80. package/src/serializer.js +279 -0
  81. package/src/serializer.test.js +229 -0
  82. package/src/token_store.js +15 -0
  83. package/src/types.js +108 -0
  84. package/src/types.test.js +75 -0
  85. package/src/utils.js +68 -0
  86. package/src/utils.test.js +85 -0
  87. package/webpack.config.babel.js +47 -0
@@ -0,0 +1,279 @@
1
+ /* eslint no-underscore-dangle: ["error", { "allow": ["_sdkType"] }] */
2
+
3
+ import transit from 'transit-js';
4
+ import _ from 'lodash';
5
+ import { UUID, LatLng, Money, BigDecimal, toType } from './types';
6
+
7
+ /**
8
+ Composes two readers (sdk type and app type) so that:
9
+
10
+ ```
11
+ class MyCustomUuid {
12
+ constructor(uuid) {
13
+ this.myUuid = uuid;
14
+ }
15
+ }
16
+
17
+ const sdkTypeReader = {
18
+ sdkType: UUID,
19
+ reader: v => new UUID(v),
20
+ };
21
+
22
+ const appTypeReader = {
23
+ sdkType: UUID,
24
+
25
+ // type of reader function: UUID -> MyCustomUuid
26
+ reader: v => new MyCustomUuid(v.uuid),
27
+ }
28
+
29
+ Composition creates a new reader:
30
+
31
+ {
32
+ sdkType: UUID,
33
+ reader: v => new MyCustomUuid(new UUID(v))
34
+ }
35
+ ```
36
+ */
37
+ const composeReader = (sdkTypeReader, appTypeReader) => {
38
+ const sdkTypeReaderFn = sdkTypeReader.reader;
39
+ const appTypeReaderFn = appTypeReader ? appTypeReader.reader : _.identity;
40
+
41
+ return rep => appTypeReaderFn(sdkTypeReaderFn(rep));
42
+ };
43
+
44
+ /**
45
+ Type map from Transit tags to type classes
46
+ */
47
+ const typeMap = {
48
+ u: UUID,
49
+ geo: LatLng,
50
+ mn: Money,
51
+ f: BigDecimal,
52
+ };
53
+
54
+ /**
55
+ List of SDK type readers
56
+ */
57
+ const sdkTypeReaders = [
58
+ {
59
+ sdkType: UUID,
60
+ reader: rep => new UUID(rep),
61
+ },
62
+ {
63
+ sdkType: LatLng,
64
+ reader: ([lat, lng]) => new LatLng(lat, lng),
65
+ },
66
+ {
67
+ sdkType: Money,
68
+ reader: ([amount, currency]) => new Money(amount, currency),
69
+ },
70
+ {
71
+ sdkType: BigDecimal,
72
+ reader: rep => new BigDecimal(rep),
73
+ },
74
+ ];
75
+
76
+ /**
77
+ List of SDK type writers
78
+ */
79
+ const sdkTypeWriters = [
80
+ {
81
+ sdkType: UUID,
82
+ writer: v => v.uuid,
83
+ },
84
+ {
85
+ sdkType: LatLng,
86
+ writer: v => [v.lat, v.lng],
87
+ },
88
+ {
89
+ sdkType: Money,
90
+ writer: v => [v.amount, v.currency],
91
+ },
92
+ {
93
+ sdkType: BigDecimal,
94
+ writer: v => v.value,
95
+ },
96
+ ];
97
+
98
+ /**
99
+ Take `appTypeReaders` param and construct a list of read handlers
100
+ from `appTypeReaders`, `sdkTypeReaders` and `typeMap`.
101
+ */
102
+ const constructReadHandlers = appTypeReaders =>
103
+ _.fromPairs(
104
+ _.map(typeMap, (typeClass, tag) => {
105
+ const sdkTypeReader = _.find(sdkTypeReaders, r => r.sdkType === typeClass);
106
+ const appTypeReader = _.find(appTypeReaders, r => r.sdkType === typeClass);
107
+
108
+ return [tag, composeReader(sdkTypeReader, appTypeReader)];
109
+ })
110
+ );
111
+
112
+ const writeHandlers = _.flatten(
113
+ _.map(typeMap, (typeClass, tag) => {
114
+ const sdkTypeWriter = _.find(sdkTypeWriters, w => w.sdkType === typeClass);
115
+
116
+ const handler = transit.makeWriteHandler({
117
+ tag: () => tag,
118
+ rep: sdkTypeWriter.writer,
119
+ });
120
+
121
+ return [typeClass, handler];
122
+ })
123
+ );
124
+
125
+ /**
126
+ Builds JS objects from Transit maps
127
+ */
128
+ const mapBuilder = {
129
+ init: () => ({}),
130
+ add: (ret, key, val) => {
131
+ /* eslint-disable no-param-reassign */
132
+ ret[key] = val;
133
+ return ret;
134
+ },
135
+ finalize: _.identity,
136
+ };
137
+
138
+ export const reader = (appTypeReaders = []) => {
139
+ const handlers = constructReadHandlers(appTypeReaders);
140
+
141
+ return transit.reader('json', {
142
+ handlers: {
143
+ ...handlers,
144
+
145
+ // Convert keywords to plain strings.
146
+ // The conversion loses the information that the
147
+ // string was originally a keyword. However, the API
148
+ // can coerse strings to keywords, so it's ok to send strings
149
+ // to the API when keywords is expected.
150
+ ':': rep => rep,
151
+
152
+ // Convert set to an array
153
+ // The conversion loses the information that the
154
+ // array was originally a set. However, the API
155
+ // can coerse arrays to sets, so it's ok to send arrays
156
+ // to the API when set is expected.
157
+ set: rep => rep,
158
+
159
+ // Convert list to an array
160
+ list: rep => rep,
161
+ },
162
+ mapBuilder,
163
+ });
164
+ };
165
+
166
+ const MapHandler = [
167
+ Object,
168
+ transit.makeWriteHandler({
169
+ tag: () => 'map',
170
+ rep: v =>
171
+ _.reduce(
172
+ v,
173
+ (map, val, key) => {
174
+ map.set(transit.keyword(key), val);
175
+ return map;
176
+ },
177
+ transit.map()
178
+ ),
179
+ }),
180
+ ];
181
+
182
+ export const writer = (appTypeWriters = [], opts = {}) => {
183
+ const { verbose } = opts;
184
+ const transitType = verbose ? 'json-verbose' : 'json';
185
+
186
+ return transit.writer(transitType, {
187
+ handlers: transit.map([...writeHandlers, ...MapHandler]),
188
+
189
+ // Use transform to transform app types to sdk types before sdk
190
+ // types are encoded by transit.
191
+ transform: v => {
192
+ // Check _.isObject for two reasons:
193
+ // 1. _.isObject makes sure the value is not null, so the null check can be omitted in the canHandle implementation
194
+ // 2. Perf. No need to run canHandle for primitives
195
+ if (_.isObject(v)) {
196
+ if (v._sdkType) {
197
+ return toType(v);
198
+ }
199
+
200
+ const appTypeWriter = _.find(
201
+ appTypeWriters,
202
+ w =>
203
+ // Check if the value is an application type instance
204
+ (w.appType && v instanceof w.appType) ||
205
+ // ...or if the canHandle returns true.
206
+ (w.canHandle && w.canHandle(v))
207
+ );
208
+
209
+ if (appTypeWriter) {
210
+ return appTypeWriter.writer(v);
211
+ }
212
+ }
213
+
214
+ return v;
215
+ },
216
+
217
+ // This is only needed for the REPL
218
+ // TODO This could be stripped out for production build
219
+ handlerForForeign: (x, handlers) => {
220
+ if (Array.isArray(x)) {
221
+ return handlers.get('array');
222
+ }
223
+ if (typeof x === 'object') {
224
+ return handlers.get('map');
225
+ }
226
+
227
+ return null;
228
+ },
229
+ });
230
+ };
231
+
232
+ export const createTransitConverters = (typeHandlers = [], opts) => {
233
+ const { readers, writers } = typeHandlers.reduce(
234
+ (memo, handler) => {
235
+ const r = {
236
+ sdkType:
237
+ handler.sdkType ||
238
+ // DEPRECATED Use handler.sdkType instead of handler.type
239
+ handler.type,
240
+ reader: handler.reader,
241
+ };
242
+ const w = {
243
+ sdkType:
244
+ handler.sdkType ||
245
+ // DEPRECATED Use handler.sdkType instead of handler.type
246
+ handler.type,
247
+ appType:
248
+ handler.appType ||
249
+ // DEPRECATED Use handler.appType instead of handler.customType
250
+ handler.customType,
251
+ canHandle: handler.canHandle,
252
+ writer: handler.writer,
253
+ };
254
+
255
+ memo.readers.push(r);
256
+ memo.writers.push(w);
257
+
258
+ return memo;
259
+ },
260
+ { readers: [], writers: [] }
261
+ );
262
+
263
+ return {
264
+ reader: reader(readers),
265
+ writer: writer(writers, opts),
266
+ };
267
+ };
268
+
269
+ export const read = (str, opts = {}) => {
270
+ const { typeHandlers = [] } = opts;
271
+ const converters = createTransitConverters(typeHandlers);
272
+ return converters.reader.read(str);
273
+ };
274
+
275
+ export const write = (data, opts = {}) => {
276
+ const { typeHandlers = [], verbose = false } = opts;
277
+ const converters = createTransitConverters(typeHandlers, { verbose });
278
+ return converters.writer.write(data);
279
+ };
@@ -0,0 +1,229 @@
1
+ import transit from 'transit-js';
2
+ import { reader, writer } from './serializer';
3
+ import { UUID, LatLng, Money, BigDecimal } from './types';
4
+
5
+ describe('serializer', () => {
6
+ it('reads and writes transit', () => {
7
+ const testData = {
8
+ a: 1,
9
+ b: 2,
10
+ c: [3, 4, 5],
11
+ d: {
12
+ e: true,
13
+ },
14
+ };
15
+
16
+ const r = reader();
17
+ const w = writer();
18
+
19
+ expect(r.read(w.write(testData))).toEqual(testData);
20
+ });
21
+
22
+ it('reads and writes transit JSON verbose', () => {
23
+ const testData = {
24
+ a: 1,
25
+ b: 2,
26
+ c: [3, 4, 5],
27
+ d: {
28
+ e: true,
29
+ },
30
+ };
31
+
32
+ const r = reader();
33
+ const w = writer([], { verbose: true });
34
+
35
+ expect(r.read(w.write(testData))).toEqual(testData);
36
+ });
37
+
38
+ it('writes map keys as symbols', () => {
39
+ const testData = {
40
+ a: 1,
41
+ b: { c: { d: 2 } }, // handles nested objects recursively
42
+ e: [{ f: 3 }, { g: 4 }], // handles nested objects in arrays
43
+ };
44
+
45
+ const expectedData = transit.map();
46
+ expectedData.set(transit.keyword('a'), 1);
47
+
48
+ const b = transit.map();
49
+ const c = transit.map();
50
+
51
+ c.set(transit.keyword('d'), 2);
52
+ b.set(transit.keyword('c'), c);
53
+
54
+ const f = transit.map();
55
+ f.set(transit.keyword('f'), 3);
56
+ const g = transit.map();
57
+ g.set(transit.keyword('g'), 4);
58
+
59
+ expectedData.set(transit.keyword('b'), b);
60
+ expectedData.set(transit.keyword('e'), [f, g]);
61
+
62
+ const w = writer();
63
+ const transitReader = transit.reader();
64
+
65
+ expect(transit.equals(transitReader.read(w.write(testData)), expectedData)).toEqual(true);
66
+ });
67
+
68
+ it('decodes a set to an array', () => {
69
+ const testData = transit.set(['b', 'a', 'b', 'b']);
70
+
71
+ const decoded = reader().read(transit.writer().write(testData));
72
+
73
+ expect(decoded).toHaveLength(2);
74
+ expect(decoded).toEqual(expect.arrayContaining(['a', 'b']));
75
+ });
76
+
77
+ it('decodes a list to an array', () => {
78
+ const testData = transit.list(['a', 'b']);
79
+
80
+ const decoded = reader().read(transit.writer().write(testData));
81
+
82
+ expect(decoded).toEqual(expect.arrayContaining(['a', 'b']));
83
+ });
84
+
85
+ it('handles UUIDs', () => {
86
+ const testData = {
87
+ id: new UUID('69c3d77a-db3f-11e6-bf26-cec0c932ce01'),
88
+ };
89
+
90
+ const roundTrip = reader().read(writer().write(testData));
91
+ expect(roundTrip).toEqual(testData);
92
+ expect(roundTrip.id).toBeInstanceOf(UUID);
93
+ });
94
+
95
+ it('handles LatLngs', () => {
96
+ const testData = {
97
+ location: new LatLng(12.34, 56.78),
98
+ };
99
+
100
+ const roundTrip = reader().read(writer().write(testData));
101
+ expect(roundTrip).toEqual(testData);
102
+ expect(roundTrip.location).toBeInstanceOf(LatLng);
103
+ });
104
+
105
+ it('handles Money', () => {
106
+ const testData = {
107
+ price: new Money(5000, 'EUR'),
108
+ };
109
+
110
+ const roundTrip = reader().read(writer().write(testData));
111
+ expect(roundTrip).toEqual(testData);
112
+ expect(roundTrip.price).toBeInstanceOf(Money);
113
+ });
114
+
115
+ it('handles BigDecimals', () => {
116
+ const testData = {
117
+ percentage: new BigDecimal('1.00000000000000000000000000001'),
118
+ };
119
+
120
+ const roundTrip = reader().read(writer().write(testData));
121
+ expect(roundTrip).toEqual(testData);
122
+
123
+ expect(roundTrip.percentage).toBeInstanceOf(BigDecimal);
124
+ });
125
+
126
+ it('handles types that are plain objects', () => {
127
+ const uuid = '69c3d77a-db3f-11e6-bf26-cec0c932ce01';
128
+
129
+ const roundTrip = reader().read(writer().write({ uuid, _sdkType: 'UUID' }));
130
+ expect(roundTrip).toEqual(new UUID(uuid));
131
+ expect(roundTrip).toBeInstanceOf(UUID);
132
+ });
133
+
134
+ it('allows you to add your own reader handlers for predefined types', () => {
135
+ class MyCustomUuid {
136
+ constructor(str) {
137
+ this.myCustomerUuidRepresentation = str;
138
+ }
139
+ }
140
+
141
+ const r = reader([
142
+ {
143
+ sdkType: UUID,
144
+ reader: v => new MyCustomUuid(v.uuid),
145
+ },
146
+ ]);
147
+
148
+ const w = writer();
149
+
150
+ const data = '69c3d77a-db3f-11e6-bf26-cec0c932ce01';
151
+
152
+ expect(r.read(w.write({ id: new UUID(data) })).id).toEqual(new MyCustomUuid(data));
153
+ });
154
+
155
+ it('allows you to add your own writers handlers for predefined types', () => {
156
+ class MyCustomUuid {
157
+ constructor(str) {
158
+ this.myCustomUuidRepresentation = str;
159
+ }
160
+ }
161
+
162
+ const r = reader();
163
+
164
+ const w = writer([
165
+ {
166
+ sdkType: UUID,
167
+ appType: MyCustomUuid,
168
+ writer: v => new UUID(v.myCustomUuidRepresentation),
169
+ },
170
+ ]);
171
+
172
+ const data = '69c3d77a-db3f-11e6-bf26-cec0c932ce01';
173
+
174
+ expect(r.read(w.write({ id: new MyCustomUuid(data) })).id).toEqual(new UUID(data));
175
+ });
176
+
177
+ it('allows you to add your own writer handlers for predefined types using plain objects', () => {
178
+ const myUuid = uuid => ({
179
+ myType: 'My plain object UUID type',
180
+ myUuidValue: uuid,
181
+ });
182
+
183
+ const r = reader();
184
+
185
+ const w = writer([
186
+ {
187
+ sdkType: UUID,
188
+ canHandle: v => v.myType === 'My plain object UUID type',
189
+ writer: v => new UUID(v.myUuidValue),
190
+ },
191
+ ]);
192
+
193
+ const data = '69c3d77a-db3f-11e6-bf26-cec0c932ce01';
194
+
195
+ expect(r.read(w.write({ id: myUuid(data) })).id).toEqual(new UUID(data));
196
+
197
+ // Test that adding a custom writer doesn't break the default writer
198
+ expect(r.read(w.write({ id: new UUID(data) })).id).toEqual(new UUID(data));
199
+ });
200
+
201
+ it('allows you to add your own writer handlers for any type of data', () => {
202
+ const myArrayMoney = ['_my_money', 100, 'USD'];
203
+ const myFnUuid = uuid => {
204
+ const fn = () => uuid;
205
+ fn.isMyFnUuid = true;
206
+ return fn;
207
+ };
208
+
209
+ const r = reader();
210
+
211
+ const w = writer([
212
+ {
213
+ appType: UUID,
214
+ canHandle: v => v.isMyFnUuid,
215
+ writer: v => new UUID(v()),
216
+ },
217
+ {
218
+ appType: Money,
219
+ canHandle: v => v[0] === '_my_money',
220
+ writer: v => new Money(v[1], v[2]),
221
+ },
222
+ ]);
223
+
224
+ const data = '69c3d77a-db3f-11e6-bf26-cec0c932ce01';
225
+
226
+ expect(r.read(w.write({ id: myFnUuid(data) })).id).toEqual(new UUID(data));
227
+ expect(r.read(w.write({ money: myArrayMoney })).money).toEqual(new Money(100, 'USD'));
228
+ });
229
+ });
@@ -0,0 +1,15 @@
1
+ import { hasBrowserCookies } from './detect';
2
+ import browserCookieStore from './browser_cookie_store';
3
+ import memoryStore from './memory_store';
4
+
5
+ /* eslint-disable import/prefer-default-export */
6
+
7
+ export const createDefaultTokenStore = (tokenStore, clientId, secure) => {
8
+ if (hasBrowserCookies()) {
9
+ return browserCookieStore({ clientId, secure });
10
+ }
11
+
12
+ // Token store was not given and we can't use browser cookie store.
13
+ // Default to in-memory store.
14
+ return memoryStore();
15
+ };
package/src/types.js ADDED
@@ -0,0 +1,108 @@
1
+ /* eslint no-underscore-dangle: ["error", { "allow": ["_sdkType"] }] */
2
+
3
+ /**
4
+ UUID type
5
+ @constructor
6
+ @param {string} uuid - UUID represented as string
7
+ */
8
+ export class UUID {
9
+ constructor(uuid) {
10
+ this._sdkType = this.constructor._sdkType;
11
+ this.uuid = uuid;
12
+ }
13
+ }
14
+ UUID._sdkType = 'UUID';
15
+
16
+ export class LatLng {
17
+ constructor(lat, lng) {
18
+ this._sdkType = this.constructor._sdkType;
19
+ this.lat = lat;
20
+ this.lng = lng;
21
+ }
22
+ }
23
+ LatLng._sdkType = 'LatLng';
24
+
25
+ export class LatLngBounds {
26
+ constructor(ne, sw) {
27
+ this._sdkType = this.constructor._sdkType;
28
+ this.ne = ne;
29
+ this.sw = sw;
30
+ }
31
+ }
32
+ LatLngBounds._sdkType = 'LatLngBounds';
33
+
34
+ /**
35
+ Money type to represent money
36
+
37
+ - `amount`: The money amount in `minor` unit. In most cases, the minor unit means cents.
38
+ However, in currencies without cents, e.g. Japanese Yen, the `amount` value
39
+ is the number of Yens.
40
+ - `currency`: ISO 4217 currency code
41
+
42
+ Examples:
43
+
44
+ ```
45
+ new Money(5000, "USD") // $50
46
+ new Money(150, "EUR") // 1.5€
47
+ new Money(2500, "JPY") // ¥2500
48
+ ```
49
+ */
50
+ export class Money {
51
+ constructor(amount, currency) {
52
+ this._sdkType = this.constructor._sdkType;
53
+ this.amount = amount;
54
+ this.currency = currency;
55
+ }
56
+ }
57
+ Money._sdkType = 'Money';
58
+
59
+ /**
60
+ Type to represent arbitrary precision decimal value.
61
+
62
+ It's recommended to use a library such as decimal.js to make decimal
63
+ calculations.
64
+ */
65
+ export class BigDecimal {
66
+ constructor(value) {
67
+ this._sdkType = this.constructor._sdkType;
68
+ this.value = value;
69
+ }
70
+ }
71
+ BigDecimal._sdkType = 'BigDecimal';
72
+
73
+ export const toType = value => {
74
+ // eslint-disable-next-line no-underscore-dangle
75
+ const type = value && value._sdkType;
76
+
77
+ switch (type) {
78
+ case 'LatLng':
79
+ return new LatLng(value.lat, value.lng);
80
+ case 'LatLngBounds':
81
+ return new LatLngBounds(value.ne, value.sw);
82
+ case 'UUID':
83
+ return new UUID(value.uuid);
84
+ case 'Money':
85
+ return new Money(value.amount, value.currency);
86
+ case 'BigDecimal':
87
+ return new BigDecimal(value.value);
88
+ default:
89
+ return value;
90
+ }
91
+ };
92
+
93
+ //
94
+ // JSON replacer
95
+ //
96
+ // Deprecated
97
+ //
98
+ // The _sdkType field is added to the type object itself,
99
+ // so the use of replacer is not needed. The function exists purely
100
+ // for backwards compatibility. We don't want to remove it in case
101
+ // applications are using it.
102
+ //
103
+ export const replacer = (key, value) => value;
104
+
105
+ //
106
+ // JSON reviver
107
+ //
108
+ export const reviver = (key, value) => toType(value);
@@ -0,0 +1,75 @@
1
+ import { UUID, LatLng, LatLngBounds, Money, BigDecimal, replacer, reviver } from './types';
2
+
3
+ describe('JSON parse/stringify', () => {
4
+ const testData = {
5
+ uuid: new UUID('27786d1c-f16b-411b-b1fc-176969a91338'),
6
+ latlng: new LatLng(12.34, 45.56),
7
+ latlngbounds: new LatLngBounds(new LatLng(12.34, 23.45), new LatLng(34.56, 45.67)),
8
+ money: new Money(5000, 'USD'),
9
+ bigdecimal: new BigDecimal('1.00000000000000000000000000001'),
10
+ };
11
+
12
+ /* eslint-disable quote-props */
13
+ /* eslint-disable quotes */
14
+ /* eslint-disable comma-dangle */
15
+ const expectedJsonRep = {
16
+ uuid: {
17
+ uuid: '27786d1c-f16b-411b-b1fc-176969a91338',
18
+ _sdkType: 'UUID',
19
+ },
20
+ latlng: {
21
+ lat: 12.34,
22
+ lng: 45.56,
23
+ _sdkType: 'LatLng',
24
+ },
25
+ latlngbounds: {
26
+ ne: {
27
+ lat: 12.34,
28
+ lng: 23.45,
29
+ _sdkType: 'LatLng',
30
+ },
31
+ sw: {
32
+ lat: 34.56,
33
+ lng: 45.67,
34
+ _sdkType: 'LatLng',
35
+ },
36
+ _sdkType: 'LatLngBounds',
37
+ },
38
+ money: {
39
+ amount: 5000,
40
+ currency: 'USD',
41
+ _sdkType: 'Money',
42
+ },
43
+ bigdecimal: {
44
+ value: '1.00000000000000000000000000001',
45
+ _sdkType: 'BigDecimal',
46
+ },
47
+ };
48
+ /* eslint-enable quote-props */
49
+ /* eslint-enable quotes */
50
+ /* eslint-enable comma-dangle */
51
+
52
+ it('deprecated, serializes types with replacer', () => {
53
+ const jsonRep = JSON.parse(JSON.stringify(testData, replacer));
54
+
55
+ expect(jsonRep).toEqual(expectedJsonRep);
56
+ });
57
+
58
+ it('serializes types', () => {
59
+ const jsonRep = JSON.parse(JSON.stringify(testData));
60
+
61
+ expect(jsonRep).toEqual(expectedJsonRep);
62
+ });
63
+
64
+ it('deprecated. deserializes types after serializing them with replacer', () => {
65
+ const parsed = JSON.parse(JSON.stringify(testData, replacer), reviver);
66
+
67
+ expect(parsed).toEqual(testData);
68
+ });
69
+
70
+ it('deserializes types', () => {
71
+ const parsed = JSON.parse(JSON.stringify(testData), reviver);
72
+
73
+ expect(parsed).toEqual(testData);
74
+ });
75
+ });