pryv 3.1.0 → 3.3.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/README.md CHANGED
@@ -57,6 +57,7 @@ Other distributions available:
57
57
 
58
58
  - Socket.IO: [NPM package](https://www.npmjs.com/package/@pryv/socket.io), [README](https://github.com/pryv/lib-js/tree/master/components/pryv-socket.io#readme)
59
59
  - Monitor: [NPM package](https://www.npmjs.com/package/@pryv/monitor), [README](https://github.com/pryv/lib-js/tree/master/components/pryv-monitor#readme)
60
+ - CMC (Cross-account Messaging & Consent) client helpers: [NPM package](https://www.npmjs.com/package/@pryv/cmc), [README](https://github.com/pryv/lib-js/tree/master/components/pryv-cmc#readme)
60
61
 
61
62
 
62
63
  ### Quick example
@@ -677,6 +678,7 @@ The project is structured as a monorepo with components (a.k.a. workspaces in NP
677
678
  - `pryv`: the library
678
679
  - `pryv-socket.io`: Socket.IO add-on
679
680
  - `pryv-monitor`: Monitor add-on
681
+ - `pryv-cmc`: CMC (Cross-account Messaging & Consent) client helpers
680
682
 
681
683
 
682
684
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pryv",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "description": "Pryv JavaScript library",
5
5
  "keywords": [
6
6
  "Pryv",
package/src/index.d.ts CHANGED
@@ -1069,10 +1069,24 @@ declare module 'pryv' {
1069
1069
  token: string | null;
1070
1070
  };
1071
1071
 
1072
+ /**
1073
+ * Decomposed form of an APIEndpoint, returned by
1074
+ * `pryv.utils.decomposeAPIEndpoint`. `host` is the canonical platform
1075
+ * host (no `<username>.` subdomain prefix in subdomain-style
1076
+ * deployments) — the same identity cross-account features (CMC
1077
+ * counterparty slugs, etc.) key on.
1078
+ */
1079
+ export type DecomposedAPIEndpoint = {
1080
+ token: string | null;
1081
+ username: string | null;
1082
+ host: string;
1083
+ };
1084
+
1072
1085
  export const utils: {
1073
1086
  isBrowser(): boolean;
1074
1087
  extractTokenAndAPIEndpoint(apiEndpoint: string): TokenAndAPIEndpoint;
1075
1088
  buildAPIEndpoint(tokenAndAPI: TokenAndAPIEndpoint): string;
1089
+ decomposeAPIEndpoint(apiEndpoint: string, serviceInfoApi: string): DecomposedAPIEndpoint;
1076
1090
  browserIsMobileOrTablet(navigator?: string | Navigator): boolean;
1077
1091
  cleanURLFromPrYvParams(url: string): string;
1078
1092
  getQueryParamsFromURL(url: string): KeyValue;
@@ -1107,6 +1121,7 @@ declare module 'pryv' {
1107
1121
  isBrowser(): boolean;
1108
1122
  extractTokenAndAPIEndpoint(apiEndpoint: string): TokenAndAPIEndpoint;
1109
1123
  buildAPIEndpoint(tokenAndAPI: TokenAndAPIEndpoint): string;
1124
+ decomposeAPIEndpoint(apiEndpoint: string, serviceInfoApi: string): DecomposedAPIEndpoint;
1110
1125
  browserIsMobileOrTablet(navigator?: string | Navigator): boolean;
1111
1126
  cleanURLFromPrYvParams(url: string): string;
1112
1127
  getQueryParamsFromURL(url: string): KeyValue;
package/src/utils.js CHANGED
@@ -127,6 +127,76 @@ const utils = module.exports = {
127
127
  return res[1] + '://' + tokenAndAPI.token + '@' + res[2];
128
128
  },
129
129
 
130
+ /**
131
+ * Decompose a Pryv apiEndpoint into `{ token, username, host }` using
132
+ * the platform's `service.info.api` URL template to invert whichever
133
+ * username placement the platform uses (subdomain vs path-style).
134
+ *
135
+ * Pryv apiEndpoints follow one of two URL shapes (the difference is
136
+ * platform-defined, encoded in `service.info.api`):
137
+ *
138
+ * subdomain template `https://{username}.<domain>/`
139
+ * endpoint `https://<token>@<username>.<domain>/`
140
+ * path-style template `https://<host>/{username}/`
141
+ * endpoint `https://<token>@<host>/<username>/`
142
+ *
143
+ * Returns the **canonical platform host** (no `<username>.` subdomain
144
+ * prefix in subdomain mode) — the identity cross-account features
145
+ * (e.g. CMC counterparty slugs) key on, regardless of which user the
146
+ * endpoint belongs to.
147
+ *
148
+ * `token` and `username` are null when the endpoint carries no token /
149
+ * when the endpoint shape doesn't match the template; `host` is
150
+ * best-effort in that case.
151
+ *
152
+ * @memberof pryv.utils
153
+ * @param {APIEndpoint} apiEndpoint e.g. 'https://t0k3n@alice.pryv.me/'
154
+ * @param {string} serviceInfoApi e.g. 'https://{username}.pryv.me/'
155
+ * from /service/info → field `api`.
156
+ * @returns {DecomposedAPIEndpoint}
157
+ *
158
+ * @example
159
+ * const conn = new pryv.Connection(apiEndpoint);
160
+ * const info = await conn.service.info();
161
+ * const me = pryv.utils.decomposeAPIEndpoint(apiEndpoint, info.api);
162
+ * // → { token: 't0k3n', username: 'alice', host: 'pryv.me' }
163
+ */
164
+ decomposeAPIEndpoint: function (apiEndpoint, serviceInfoApi) {
165
+ const { token, endpoint } = utils.extractTokenAndAPIEndpoint(apiEndpoint);
166
+ const tplIdx = serviceInfoApi.indexOf('{username}');
167
+ if (tplIdx < 0) {
168
+ // Template doesn't carry {username} — operator-defined, can't decompose.
169
+ let host = '';
170
+ try { host = new URL(endpoint).host; } catch (_e) {}
171
+ return { token, username: null, host };
172
+ }
173
+ const tplPrefix = serviceInfoApi.slice(0, tplIdx);
174
+ const tplSuffix = serviceInfoApi.slice(tplIdx + '{username}'.length);
175
+ if (!endpoint.startsWith(tplPrefix) || !endpoint.endsWith(tplSuffix)) {
176
+ let host = '';
177
+ try { host = new URL(endpoint).host; } catch (_e) {}
178
+ return { token, username: null, host };
179
+ }
180
+ const username = endpoint.slice(tplPrefix.length, endpoint.length - tplSuffix.length);
181
+ // Disambiguate by where {username} sits in the template:
182
+ // - subdomain → prefix ends with `://` (username right after scheme)
183
+ // - path-style → prefix has more after `://` (host already in prefix)
184
+ const isSubdomainTemplate = /:\/\/$/.test(tplPrefix);
185
+ let host;
186
+ if (isSubdomainTemplate) {
187
+ // tplSuffix starts with the domain (e.g. '.pryv.me/')
188
+ host = tplSuffix.replace(/^\.+/, '').replace(/\/+$/, '');
189
+ } else {
190
+ // path-style — host is in the prefix's URL
191
+ try {
192
+ host = new URL(tplPrefix).host;
193
+ } catch (_e) {
194
+ host = '';
195
+ }
196
+ }
197
+ return { token, username, host };
198
+ },
199
+
130
200
  /**
131
201
  * Check if the browser is running on a mobile device or tablet
132
202
  * @memberof pryv.utils
@@ -251,6 +321,18 @@ utils.buildPryvApiEndpoint = utils.buildAPIEndpoint;
251
321
  * @typedef {string} APIEndpoint
252
322
  */
253
323
 
324
+ /**
325
+ * Decomposed form of an APIEndpoint, returned by `decomposeAPIEndpoint`.
326
+ * `token` and `username` are `null` when the endpoint doesn't carry a
327
+ * token / doesn't match the `service.info.api` template. `host` is the
328
+ * canonical platform host (no `<username>.` subdomain prefix in
329
+ * subdomain-style deployments).
330
+ * @typedef {Object} DecomposedAPIEndpoint
331
+ * @property {string|null} token
332
+ * @property {string|null} username
333
+ * @property {string} host
334
+ */
335
+
254
336
  /**
255
337
  * Common Meta are returned by each standard call on the API https://api.pryv.com/reference/#in-method-results
256
338
  * @typedef {Object} CommonMeta
@@ -103,4 +103,41 @@ describe('[UTLX] utils', function () {
103
103
  expect(err.data.currentSerial).to.equal(2);
104
104
  expect(err.name).to.equal('StaleAccessIdError');
105
105
  });
106
+
107
+ describe('[UTLM] decomposeAPIEndpoint', function () {
108
+ it('[UTLMA] subdomain template — host strips the {username}. prefix', function () {
109
+ const r = pryv.utils.decomposeAPIEndpoint(
110
+ 'https://t0k3n@alice.pryv.me/',
111
+ 'https://{username}.pryv.me/'
112
+ );
113
+ expect(r).to.eql({ token: 't0k3n', username: 'alice', host: 'pryv.me' });
114
+ });
115
+
116
+ it('[UTLMB] path-style template — host is the literal hostname[:port]', function () {
117
+ const r = pryv.utils.decomposeAPIEndpoint(
118
+ 'http://t0k3n@127.0.0.1:3000/alice/',
119
+ 'http://127.0.0.1:3000/{username}/'
120
+ );
121
+ expect(r).to.eql({ token: 't0k3n', username: 'alice', host: '127.0.0.1:3000' });
122
+ });
123
+
124
+ it('[UTLMC] returns username:null when the endpoint doesn\'t match the template', function () {
125
+ const r = pryv.utils.decomposeAPIEndpoint(
126
+ 'https://t0k3n@somewhere.else.com/',
127
+ 'https://{username}.pryv.me/'
128
+ );
129
+ expect(r.username).to.equal(null);
130
+ expect(r.token).to.equal('t0k3n');
131
+ });
132
+
133
+ it('[UTLMD] returns token:null when endpoint carries no token', function () {
134
+ const r = pryv.utils.decomposeAPIEndpoint(
135
+ 'https://alice.pryv.me/',
136
+ 'https://{username}.pryv.me/'
137
+ );
138
+ expect(r.token).to.equal(null);
139
+ expect(r.username).to.equal('alice');
140
+ expect(r.host).to.equal('pryv.me');
141
+ });
142
+ });
106
143
  });