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.
- package/.circleci/config.yml +22 -0
- package/.eslintignore +3 -0
- package/.eslintrc.js +19 -0
- package/CHANGELOG.md +231 -0
- package/LICENSE +201 -0
- package/README.md +76 -0
- package/build/sharetribe-flex-sdk-node.js +13592 -0
- package/build/sharetribe-flex-sdk-web.js +1 -0
- package/docs/README.md +20 -0
- package/docs/authentication.md +179 -0
- package/docs/calling-the-api.md +217 -0
- package/docs/configurations.md +95 -0
- package/docs/developing-sdk.md +131 -0
- package/docs/docpress.json +14 -0
- package/docs/features.md +14 -0
- package/docs/keep-alive.md +48 -0
- package/docs/object-query-parameters.md +36 -0
- package/docs/scripts.js +41 -0
- package/docs/serializing-types-to-json.md +40 -0
- package/docs/sharing-session-between-client-and-server.md +19 -0
- package/docs/styles.css +95 -0
- package/docs/token-store.md +114 -0
- package/docs/try-it-in-browser.md +32 -0
- package/docs/try-it-in-the-playground.md +153 -0
- package/docs/types.md +27 -0
- package/docs/writing-your-own-token-store.md +29 -0
- package/docs/your-own-types.md +61 -0
- package/examples/README.md +5 -0
- package/examples/getting-started-browser/README.md +22 -0
- package/examples/getting-started-browser/index.html +89 -0
- package/examples/getting-started-browser/index.js +156 -0
- package/examples/getting-started-browser/screenshots/screenshot1.png +0 -0
- package/examples/getting-started-browser/screenshots/screenshot2.png +0 -0
- package/examples/getting-started-node/README.md +23 -0
- package/examples/getting-started-node/index.js +139 -0
- package/examples/getting-started-node/screenshots/screenshot.png +0 -0
- package/package.json +83 -0
- package/playground.js +295 -0
- package/src/browser_cookie_store.js +26 -0
- package/src/context_runner.js +151 -0
- package/src/context_runner.test.js +185 -0
- package/src/detect.js +11 -0
- package/src/express_cookie_store.js +57 -0
- package/src/fake/adapter.js +130 -0
- package/src/fake/api.js +137 -0
- package/src/fake/auth.js +84 -0
- package/src/fake/token_store.js +231 -0
- package/src/index.js +25 -0
- package/src/interceptors/.eslintrc.js +5 -0
- package/src/interceptors/add_auth_header.js +32 -0
- package/src/interceptors/add_auth_header.test.js +50 -0
- package/src/interceptors/add_auth_token_response.js +16 -0
- package/src/interceptors/add_client_id_to_params.js +12 -0
- package/src/interceptors/add_client_secret_to_params.js +15 -0
- package/src/interceptors/add_grant_type_to_params.js +23 -0
- package/src/interceptors/add_idp_client_id_to_params.js +17 -0
- package/src/interceptors/add_idp_id_to_params.js +17 -0
- package/src/interceptors/add_idp_token_to_params.js +17 -0
- package/src/interceptors/add_scope_to_params.js +18 -0
- package/src/interceptors/add_subject_token_to_params.js +18 -0
- package/src/interceptors/add_token_exchange_grant_type_to_params.js +12 -0
- package/src/interceptors/auth_info.js +50 -0
- package/src/interceptors/clear_token_after_revoke.js +45 -0
- package/src/interceptors/default_params.js +12 -0
- package/src/interceptors/fetch_auth_token_from_api.js +33 -0
- package/src/interceptors/fetch_auth_token_from_store.js +27 -0
- package/src/interceptors/fetch_refresh_token_for_revoke.js +24 -0
- package/src/interceptors/multipart_request.js +35 -0
- package/src/interceptors/retry_with_anon_token.js +58 -0
- package/src/interceptors/retry_with_refresh_token.js +70 -0
- package/src/interceptors/save_token.js +20 -0
- package/src/interceptors/transit_request.js +27 -0
- package/src/interceptors/transit_request.test.js +58 -0
- package/src/interceptors/transit_response.js +27 -0
- package/src/memory_store.js +19 -0
- package/src/params_serializer.js +65 -0
- package/src/params_serializer.test.js +58 -0
- package/src/sdk.js +894 -0
- package/src/sdk.test.js +908 -0
- package/src/serializer.js +279 -0
- package/src/serializer.test.js +229 -0
- package/src/token_store.js +15 -0
- package/src/types.js +108 -0
- package/src/types.test.js +75 -0
- package/src/utils.js +68 -0
- package/src/utils.test.js +85 -0
- 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
|
+
});
|