@de./sdk 2.1.0 → 2.1.1
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/dist/allend/Access.js +20 -5
- package/dist/allend/Access.js.map +1 -1
- package/dist/allend/LSP/agents.d.ts +14 -1
- package/dist/allend/LSP/agents.js +38 -0
- package/dist/allend/LSP/agents.js.map +1 -1
- package/dist/allend/MSI/Handles.d.ts +10 -2
- package/dist/allend/MSI/Handles.js +11 -3
- package/dist/allend/MSI/Handles.js.map +1 -1
- package/dist/allend/MSI/index.d.ts +10 -0
- package/dist/allend/MSI/index.js +19 -0
- package/dist/allend/MSI/index.js.map +1 -1
- package/dist/allend/MSI/native.js +69 -35
- package/dist/allend/MSI/native.js.map +1 -1
- package/dist/allend/MSI/origin.d.ts +15 -0
- package/dist/allend/MSI/origin.js +25 -0
- package/dist/allend/MSI/origin.js.map +1 -0
- package/dist/allend/OTPAuth/index.d.ts +12 -18
- package/dist/allend/OTPAuth/index.js +17 -7
- package/dist/allend/OTPAuth/index.js.map +1 -1
- package/dist/utils/index.d.ts +14 -0
- package/dist/utils/index.js +24 -4
- package/dist/utils/index.js.map +1 -1
- package/package.json +12 -6
- package/src/allend/Access.ts +20 -4
- package/src/allend/LSP/agents.ts +46 -1
- package/src/allend/MSI/Handles.ts +12 -4
- package/src/allend/MSI/index.ts +20 -0
- package/src/allend/MSI/native.tsx +73 -34
- package/src/allend/MSI/origin.ts +20 -0
- package/src/allend/OTPAuth/index.ts +33 -25
- package/src/utils/index.ts +27 -8
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
import type { SigninBody, CreateAccountBody } from '@de./types';
|
|
1
2
|
import type { AccessOptions, UserSession } from '../../types/access';
|
|
2
3
|
import AccessManager from '../Access';
|
|
3
4
|
import { type Res } from '../../utils';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Request bodies come from `@de./types`, which is what de.auth validates
|
|
7
|
+
* against — these were hand-declared here, and said `country?: string` where
|
|
8
|
+
* de.auth wants the Country OBJECT. Its schema is additionalProperties:false,
|
|
9
|
+
* so a caller trusting the SDK's type was answered `body/country must be
|
|
10
|
+
* object`. Nothing caught it because this client could not be constructed at
|
|
11
|
+
* all: AccessManager demanded a workspace context and a bearer token, which
|
|
12
|
+
* sign-in is what produces.
|
|
13
|
+
*/
|
|
14
|
+
export type { SigninBody, CreateAccountBody, VerificationBody, Country } from '@de./types';
|
|
11
15
|
export type SigninResult = {
|
|
12
16
|
/** What the caller must do next — verification, or account creation first. */
|
|
13
17
|
next: string;
|
|
@@ -19,16 +23,6 @@ export type SigninResult = {
|
|
|
19
23
|
*/
|
|
20
24
|
testVCode?: number;
|
|
21
25
|
};
|
|
22
|
-
export type CreateAccountBody = {
|
|
23
|
-
phone: string;
|
|
24
|
-
agreeTerms: boolean | string;
|
|
25
|
-
firstName?: string;
|
|
26
|
-
lastName?: string;
|
|
27
|
-
photo?: string;
|
|
28
|
-
type?: string;
|
|
29
|
-
country?: string;
|
|
30
|
-
newsletters?: boolean | string;
|
|
31
|
-
};
|
|
32
26
|
export type VerifyResult = {
|
|
33
27
|
ctoken?: string;
|
|
34
28
|
deviceId?: string;
|
|
@@ -51,7 +45,7 @@ export default class OTPAuth extends AccessManager {
|
|
|
51
45
|
resendSms(body: {
|
|
52
46
|
phone: string;
|
|
53
47
|
}): Promise<Res<void>>;
|
|
54
|
-
/**
|
|
48
|
+
/** A state change, so a POST — it was a GET, which is cacheable. */
|
|
55
49
|
signout(querystring?: {
|
|
56
50
|
allDevices?: boolean | string;
|
|
57
51
|
}): Promise<Res<void>>;
|
|
@@ -7,29 +7,39 @@ const Access_1 = __importDefault(require("../Access"));
|
|
|
7
7
|
const utils_1 = require("../../utils");
|
|
8
8
|
class OTPAuth extends Access_1.default {
|
|
9
9
|
constructor(access) {
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* de.auth admits an agent that does not start with `De.service` only if
|
|
12
|
+
* the request carries an Authorization header — and the whole point of
|
|
13
|
+
* this client is the flow that has no token yet. The SDK's default
|
|
14
|
+
* platform is `proxy`, so every sign-in through it was answered 412
|
|
15
|
+
* before reaching a handler.
|
|
16
|
+
*
|
|
17
|
+
* `service` is the platform this client is for; a caller that knows
|
|
18
|
+
* better can still say so.
|
|
19
|
+
*/
|
|
20
|
+
super({ ...access, platform: access.platform || 'service' }, 'ASI');
|
|
11
21
|
}
|
|
12
22
|
// ── Sign-in ───────────────────────────────────────────────────────────────
|
|
13
23
|
/** Step 1 — sends the code. */
|
|
14
24
|
async signin(body) {
|
|
15
25
|
if (!body.phone)
|
|
16
26
|
throw new Error('<phone> argument required');
|
|
17
|
-
return this.request({ url: '/
|
|
27
|
+
return this.request({ url: '/auth/otp/send', method: 'POST', body });
|
|
18
28
|
}
|
|
19
29
|
/** Step 2 — exchanges the code for a session. */
|
|
20
30
|
async verify(body) {
|
|
21
|
-
return this.request({ url: '/
|
|
31
|
+
return this.request({ url: '/auth/otp/verify', method: 'POST', body });
|
|
22
32
|
}
|
|
23
33
|
/** Step 1a — for a phone de.auth has never seen. */
|
|
24
34
|
async setAccount(body) {
|
|
25
|
-
return this.request({ url: '/
|
|
35
|
+
return this.request({ url: '/auth/account/create', method: 'POST', body });
|
|
26
36
|
}
|
|
27
37
|
async resendSms(body) {
|
|
28
|
-
return this.request({ url: '/resend
|
|
38
|
+
return this.request({ url: '/auth/otp/resend', method: 'POST', body });
|
|
29
39
|
}
|
|
30
|
-
/**
|
|
40
|
+
/** A state change, so a POST — it was a GET, which is cacheable. */
|
|
31
41
|
async signout(querystring) {
|
|
32
|
-
return this.request({ url: `/
|
|
42
|
+
return this.request({ url: `/auth/session/revoke${(0, utils_1.qs)(querystring)}`, method: 'POST' });
|
|
33
43
|
}
|
|
34
44
|
// ── The signed-in person ──────────────────────────────────────────────────
|
|
35
45
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/allend/OTPAuth/index.ts"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/allend/OTPAuth/index.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -23,6 +23,20 @@ export type Res<T = any> = {
|
|
|
23
23
|
* property fails that test outright.
|
|
24
24
|
*/
|
|
25
25
|
export type Data<T> = 'data' extends keyof T ? T['data'] : undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Serialize a querystring for a De. route.
|
|
28
|
+
*
|
|
29
|
+
* Array values are appended as repeated keys — `state=A&state=B` — which is
|
|
30
|
+
* what Fastify's query parser turns back into an array. `String( value )` on
|
|
31
|
+
* an array produces `A,B`, a single comma-joined string, and the route answers
|
|
32
|
+
* `querystring/state must be array`: the schema is right, the request never
|
|
33
|
+
* matched it. Every array parameter the SDK sent was malformed this way, the
|
|
34
|
+
* same shape of fault as the coordinates object that once serialized to
|
|
35
|
+
* `[object Object]`.
|
|
36
|
+
*
|
|
37
|
+
* @param params - Query parameters; null and undefined values are dropped
|
|
38
|
+
* @return - `?a=1&b=2`, or an empty string when there is nothing to send
|
|
39
|
+
*/
|
|
26
40
|
export declare const qs: (params?: Record<string, any>) => string;
|
|
27
41
|
declare const _default: {
|
|
28
42
|
Stream: typeof Stream;
|
package/dist/utils/index.js
CHANGED
|
@@ -5,13 +5,33 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.qs = void 0;
|
|
7
7
|
const stream_1 = __importDefault(require("./stream"));
|
|
8
|
+
/**
|
|
9
|
+
* Serialize a querystring for a De. route.
|
|
10
|
+
*
|
|
11
|
+
* Array values are appended as repeated keys — `state=A&state=B` — which is
|
|
12
|
+
* what Fastify's query parser turns back into an array. `String( value )` on
|
|
13
|
+
* an array produces `A,B`, a single comma-joined string, and the route answers
|
|
14
|
+
* `querystring/state must be array`: the schema is right, the request never
|
|
15
|
+
* matched it. Every array parameter the SDK sent was malformed this way, the
|
|
16
|
+
* same shape of fault as the coordinates object that once serialized to
|
|
17
|
+
* `[object Object]`.
|
|
18
|
+
*
|
|
19
|
+
* @param params - Query parameters; null and undefined values are dropped
|
|
20
|
+
* @return - `?a=1&b=2`, or an empty string when there is nothing to send
|
|
21
|
+
*/
|
|
8
22
|
const qs = (params) => {
|
|
9
23
|
if (!params)
|
|
10
24
|
return '';
|
|
11
|
-
const q = new URLSearchParams(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
25
|
+
const q = new URLSearchParams();
|
|
26
|
+
for (const [key, value] of Object.entries(params)) {
|
|
27
|
+
if (value === null || value === undefined)
|
|
28
|
+
continue;
|
|
29
|
+
Array.isArray(value)
|
|
30
|
+
? value.forEach(each => each !== null && each !== undefined && q.append(key, String(each)))
|
|
31
|
+
: q.append(key, String(value));
|
|
32
|
+
}
|
|
33
|
+
const query = q.toString();
|
|
34
|
+
return query ? `?${query}` : '';
|
|
15
35
|
};
|
|
16
36
|
exports.qs = qs;
|
|
17
37
|
exports.default = { Stream: stream_1.default };
|
package/dist/utils/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":";;;;;;AACA,sDAA6B;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":";;;;;;AACA,sDAA6B;AAsB7B;;;;;;;;;;;;;GAaG;AACI,MAAM,EAAE,GAAG,CAAE,MAA4B,EAAW,EAAE;IAC3D,IAAI,CAAC,MAAM;QAAG,OAAO,EAAE,CAAA;IAEvB,MAAM,CAAC,GAAG,IAAI,eAAe,EAAE,CAAA;IAE/B,KAAK,MAAM,CAAE,GAAG,EAAE,KAAK,CAAE,IAAI,MAAM,CAAC,OAAO,CAAE,MAAM,CAAE,EAAE,CAAC;QACtD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;YAAG,SAAQ;QAEpD,KAAK,CAAC,OAAO,CAAE,KAAK,CAAE;YACpB,CAAC,CAAC,KAAK,CAAC,OAAO,CAAE,IAAI,CAAC,EAAE,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,CAAE,GAAG,EAAE,MAAM,CAAE,IAAI,CAAE,CAAE,CAAE;YACjG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAE,GAAG,EAAE,MAAM,CAAE,KAAK,CAAE,CAAE,CAAA;IACtC,CAAC;IAED,MAAM,KAAK,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAA;IAC1B,OAAO,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;AACjC,CAAC,CAAA;AAfY,QAAA,EAAE,MAed;AAED,kBAAe,EAAE,MAAM,EAAN,gBAAM,EAAE,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@de./sdk",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Complete SDK pack for MSI (Map Service Interface), IoTClient and DClient services — web, server and React Native",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@de./types": "^2.1.0",
|
|
45
45
|
"events": "^3.3.0",
|
|
46
|
-
"iframe.io": "^1.
|
|
46
|
+
"iframe.io": "^1.3.0",
|
|
47
47
|
"node-fetch": "2.6.1",
|
|
48
48
|
"socket.io-client": "^4.6.1",
|
|
49
|
-
"webview.io": "^1.
|
|
49
|
+
"webview.io": "^1.2.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"react": ">=16.8.0",
|
|
@@ -54,9 +54,15 @@
|
|
|
54
54
|
"react-native-webview": ">=11.0.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependenciesMeta": {
|
|
57
|
-
"react": {
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
"react": {
|
|
58
|
+
"optional": true
|
|
59
|
+
},
|
|
60
|
+
"react-native": {
|
|
61
|
+
"optional": true
|
|
62
|
+
},
|
|
63
|
+
"react-native-webview": {
|
|
64
|
+
"optional": true
|
|
65
|
+
}
|
|
60
66
|
},
|
|
61
67
|
"devDependencies": {
|
|
62
68
|
"@babel/core": "^7.26.0",
|
package/src/allend/Access.ts
CHANGED
|
@@ -56,10 +56,26 @@ export default class AccessManager {
|
|
|
56
56
|
|
|
57
57
|
constructor( options: AccessOptions, atype: AccessType ){
|
|
58
58
|
if( !options ) throw new Error('Undefined Access Configuration')
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A workspace context and a bearer token identify a CALLER to de.arch, and
|
|
62
|
+
* every API client needs both. de.auth answers a different question — who
|
|
63
|
+
* the PERSON is — and the sign-in flow is what produces a session in the
|
|
64
|
+
* first place: at `POST /auth/otp/send` there is no workspace to name and
|
|
65
|
+
* no token to bear.
|
|
66
|
+
*
|
|
67
|
+
* Requiring them of every access type meant `OTPAuth` threw in its own
|
|
68
|
+
* constructor and could never be used for the one thing it exists to do.
|
|
69
|
+
* That is why de.simulation's bootstrap and Hot's `getUser` both talk to
|
|
70
|
+
* de.auth over raw fetch, and why nothing ever caught that this client
|
|
71
|
+
* addressed `/v1/auth/otp` — a route de.auth has never served.
|
|
72
|
+
*/
|
|
73
|
+
if( atype !== 'ASI' ){
|
|
74
|
+
if( !options.context ) throw new Error('Undefined Context Reference. Check https://doc.dedot.io/sdk/auth')
|
|
75
|
+
if( !options.accessToken ) throw new Error('Undefined Access Token. Check https://doc.dedot.io/sdk/auth')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.context = options.context || ''
|
|
63
79
|
this.atype = atype
|
|
64
80
|
this.version = options.version || 1
|
|
65
81
|
this.platform = options.platform || 'proxy'
|
package/src/allend/LSP/agents.ts
CHANGED
|
@@ -5,7 +5,10 @@ import type {
|
|
|
5
5
|
LSPAgentUpdateValidation,
|
|
6
6
|
LSPAgentAssignVehicleValidation,
|
|
7
7
|
LSPAgentUnassignVehicleValidation,
|
|
8
|
-
LSPAgentRemoveValidation
|
|
8
|
+
LSPAgentRemoveValidation,
|
|
9
|
+
LSPAgentEnrolValidation,
|
|
10
|
+
LSPAgentEnrolmentRetrieveValidation,
|
|
11
|
+
LSPAgentEnrolmentUpdateValidation
|
|
9
12
|
} from '@de./types/aux/agents'
|
|
10
13
|
import type {
|
|
11
14
|
LSPConsolidationScheduleValidation,
|
|
@@ -22,6 +25,48 @@ import { qs, type Http, type Res, type Data } from '../../utils'
|
|
|
22
25
|
export default class LSPAgents {
|
|
23
26
|
constructor( private http: Http ){}
|
|
24
27
|
|
|
28
|
+
// ── Self-enrolment ──────────────────────────────────────────────────────
|
|
29
|
+
//
|
|
30
|
+
// The invitation flow needs an operator to name each agent, which is right
|
|
31
|
+
// when a provider picks riders one at a time and wrong for a platform that
|
|
32
|
+
// already runs its own verification. Enrolment keeps what invitation was
|
|
33
|
+
// buying — the agent lands in this provider's context, and the provider
|
|
34
|
+
// agreed to take them — but takes the agreement from a standing policy
|
|
35
|
+
// instead of a per-person act.
|
|
36
|
+
|
|
37
|
+
/** The provider's standing enrolment policy. Operator-scoped. */
|
|
38
|
+
async enrolmentPolicy(): Promise<LSPAgentEnrolmentRetrieveValidation['response']> {
|
|
39
|
+
return await this.http.request<LSPAgentEnrolmentRetrieveValidation['response']>({
|
|
40
|
+
url: '/lsp/agents/enrolment',
|
|
41
|
+
method: 'GET'
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Set it. Off unless `enabled` says otherwise. Operator-scoped. */
|
|
46
|
+
async setEnrolmentPolicy( body: LSPAgentEnrolmentUpdateValidation['body'] ): Promise<LSPAgentEnrolmentUpdateValidation['response']> {
|
|
47
|
+
return await this.http.request<LSPAgentEnrolmentUpdateValidation['response']>({
|
|
48
|
+
url: '/lsp/agents/enrolment',
|
|
49
|
+
method: 'PUT',
|
|
50
|
+
body
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Join this provider as an agent.
|
|
56
|
+
*
|
|
57
|
+
* Authorized as the person joining — the uid on the token — so a platform
|
|
58
|
+
* enrols on someone's behalf by minting for them, exactly as it would to
|
|
59
|
+
* redeem an invitation. `status`, `grade` and the zone come from the
|
|
60
|
+
* provider's policy, not from the body.
|
|
61
|
+
*/
|
|
62
|
+
async enrol( body: LSPAgentEnrolValidation['body'] ): Promise<LSPAgentEnrolValidation['response']> {
|
|
63
|
+
return await this.http.request<LSPAgentEnrolValidation['response']>({
|
|
64
|
+
url: '/lsp/agents/enrolment',
|
|
65
|
+
method: 'POST',
|
|
66
|
+
body
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
25
70
|
async list( querystring?: LSPAgentListValidation['querystring'] ): Promise<LSPAgentListValidation['response']> {
|
|
26
71
|
return await this.http.request<LSPAgentListValidation['response']>({
|
|
27
72
|
url: `/lsp/agents${qs( querystring )}`,
|
|
@@ -46,13 +46,21 @@ export default class Handles extends EventEmitter {
|
|
|
46
46
|
/**
|
|
47
47
|
* Create new stream through which user's current location
|
|
48
48
|
* details will be pushed to the API level.
|
|
49
|
-
*
|
|
50
|
-
*
|
|
49
|
+
*
|
|
50
|
+
* This took a `usertype` of `client | agent` and sent it as the payload of
|
|
51
|
+
* `pin:current:location`. The gateway's handler takes an acknowledgement in
|
|
52
|
+
* that position and its `geolocator.pin()` takes no arguments at all, so the
|
|
53
|
+
* value was bound to `callback`, failed the `typeof … == 'function'` test and
|
|
54
|
+
* was discarded — `myLocation('agent')` and `myLocation('client')` did the
|
|
55
|
+
* same thing. `Controls.pinCurrentLocation()` emits the same event correctly,
|
|
56
|
+
* with only the ack. Removed rather than kept as decoration; distinguishing
|
|
57
|
+
* the marker by user type is a gateway feature that does not exist yet.
|
|
58
|
+
*
|
|
51
59
|
* @return - Readable stream
|
|
52
60
|
*/
|
|
53
|
-
myLocation(
|
|
61
|
+
myLocation(){
|
|
54
62
|
if( !this.chn ) return
|
|
55
|
-
this.chn?.emit('pin:current:location'
|
|
63
|
+
this.chn?.emit('pin:current:location')
|
|
56
64
|
|
|
57
65
|
const stream = new Stream()
|
|
58
66
|
|
package/src/allend/MSI/index.ts
CHANGED
|
@@ -217,6 +217,26 @@ export default class MSI extends EventEmitter {
|
|
|
217
217
|
*/
|
|
218
218
|
isReady(){ return this.chn && this.isConnected }
|
|
219
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Tear the gateway down.
|
|
222
|
+
*
|
|
223
|
+
* Every `load()` adds a `message` listener to the window and an iframe to
|
|
224
|
+
* the page, and neither went away: a single-page host that mounts a map on
|
|
225
|
+
* a route accumulated one of each per visit, all of them still parsing
|
|
226
|
+
* traffic from the gateway. The React Native entry already unwinds this on
|
|
227
|
+
* unmount; the web entry had no way to.
|
|
228
|
+
*/
|
|
229
|
+
destroy(){
|
|
230
|
+
this.chn?.disconnect()
|
|
231
|
+
this.chn = undefined
|
|
232
|
+
this.isConnected = false
|
|
233
|
+
|
|
234
|
+
const container = document.getElementById( this.element )
|
|
235
|
+
if( container ) container.innerHTML = ''
|
|
236
|
+
|
|
237
|
+
this.removeAllListeners()
|
|
238
|
+
}
|
|
239
|
+
|
|
220
240
|
/**
|
|
221
241
|
* Extend the kit's functionalities
|
|
222
242
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { MapOptions } from '../../types'
|
|
2
2
|
|
|
3
|
-
import React, { useRef, useEffect, useState, useCallback, forwardRef, useImperativeHandle } from 'react'
|
|
3
|
+
import React, { useRef, useEffect, useState, useCallback, useMemo, forwardRef, useImperativeHandle } from 'react'
|
|
4
4
|
import { View, StyleSheet, AppState, type AppStateStatus, Platform } from 'react-native'
|
|
5
5
|
import { WebView } from 'react-native-webview'
|
|
6
6
|
import WIO from 'webview.io'
|
|
@@ -8,6 +8,7 @@ import Handles from './Handles'
|
|
|
8
8
|
import Controls from './Controls'
|
|
9
9
|
import Plugins, { type Plugin } from './Plugins'
|
|
10
10
|
import resolveAccessToken from './token'
|
|
11
|
+
import belongsToGateway from './origin'
|
|
11
12
|
|
|
12
13
|
export interface MSIInterface {
|
|
13
14
|
controls: Controls
|
|
@@ -115,6 +116,18 @@ export default forwardRef<MSIRef, MSIProps>( ( props, ref ) => {
|
|
|
115
116
|
apiRef = useRef<MSIInterface | null>( null ),
|
|
116
117
|
isInitializedRef = useRef( false ),
|
|
117
118
|
|
|
119
|
+
/**
|
|
120
|
+
* The bridge is built once, so its handlers close over the props of the
|
|
121
|
+
* render that built it. `bind` resolves the access token at connect time
|
|
122
|
+
* precisely so a reconnect picks up a rotated one -- but reading it from a
|
|
123
|
+
* captured `props` gave back whatever `getAccessToken` closed over at mount,
|
|
124
|
+
* which for the usual `() => token` is the token the host held then. Every
|
|
125
|
+
* reconnect after a rotation would have re-bound with a dead token. Same for
|
|
126
|
+
* the callbacks: a host passing inline handlers had the mount-time ones
|
|
127
|
+
* called forever.
|
|
128
|
+
*/
|
|
129
|
+
propsRef = useRef( props ),
|
|
130
|
+
|
|
118
131
|
[ isConnected, setIsConnected ] = useState( false ),
|
|
119
132
|
[ isReady, setIsReady ] = useState( false ),
|
|
120
133
|
|
|
@@ -128,13 +141,46 @@ export default forwardRef<MSIRef, MSIProps>( ( props, ref ) => {
|
|
|
128
141
|
? error
|
|
129
142
|
: new Error( typeof error === 'string' ? error : error?.error || 'Unknown MSI error')
|
|
130
143
|
|
|
131
|
-
|
|
132
|
-
}, [
|
|
144
|
+
propsRef.current.onError?.( normalized )
|
|
145
|
+
}, [] ),
|
|
133
146
|
|
|
134
147
|
initializeConnection = useCallback( () => {
|
|
135
148
|
if( !wioRef.current || !webViewRef.current ) return
|
|
136
149
|
wioRef.current.initiate( webViewRef, baseURL )
|
|
137
|
-
}, [ baseURL ] )
|
|
150
|
+
}, [ baseURL ] ),
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Refuse to navigate anywhere but the gateway -- see `belongsToGateway`.
|
|
154
|
+
*/
|
|
155
|
+
isGatewayURL = useCallback( ( url: string ) => belongsToGateway( baseURL, url ), [ baseURL ] ),
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* One options object for both ends of the bridge.
|
|
159
|
+
*
|
|
160
|
+
* `getInjectedJavaScript()` bakes the transport's own configuration into the
|
|
161
|
+
* script the WebView runs — the auth secret, the skew tolerance, the replay
|
|
162
|
+
* window. It used to be called on a throwaway `new WIO({ type: 'WEBVIEW' })`
|
|
163
|
+
* constructed inline in the JSX, which shares nothing with the instance that
|
|
164
|
+
* actually holds the connection: the embedded side would have been handed a
|
|
165
|
+
* null secret and default tolerances no matter what the host was configured
|
|
166
|
+
* with. Nothing configures cryptoAuth today, so it never showed.
|
|
167
|
+
*/
|
|
168
|
+
wioOptions = useMemo( () => ({
|
|
169
|
+
type: 'WEBVIEW' as const,
|
|
170
|
+
debug: props.env === 'dev',
|
|
171
|
+
allowedIncomingEvents: ALLOWED_INCOMING_EVENTS,
|
|
172
|
+
maxMessagesPerSecond: 100,
|
|
173
|
+
connectionTimeout: 15000,
|
|
174
|
+
connectionPingInterval: 2000,
|
|
175
|
+
maxConnectionAttempts: 5,
|
|
176
|
+
autoReconnect: true,
|
|
177
|
+
heartbeatInterval: 30000
|
|
178
|
+
}), [ props.env ] ),
|
|
179
|
+
|
|
180
|
+
// Rebuilt only when those options change, rather than on every render.
|
|
181
|
+
injectedBridge = useMemo( () => new WIO( wioOptions ).getInjectedJavaScript(), [ wioOptions ] )
|
|
182
|
+
|
|
183
|
+
useEffect( () => { propsRef.current = props } )
|
|
138
184
|
|
|
139
185
|
// Expose the control surface to the host
|
|
140
186
|
useImperativeHandle( ref, () => ({
|
|
@@ -149,17 +195,7 @@ export default forwardRef<MSIRef, MSIProps>( ( props, ref ) => {
|
|
|
149
195
|
}), [ isConnected, isReady, initializeConnection ] )
|
|
150
196
|
|
|
151
197
|
useEffect( () => {
|
|
152
|
-
const wio = wioRef.current = new WIO(
|
|
153
|
-
type: 'WEBVIEW',
|
|
154
|
-
debug: props.env === 'dev',
|
|
155
|
-
allowedIncomingEvents: ALLOWED_INCOMING_EVENTS,
|
|
156
|
-
maxMessagesPerSecond: 100,
|
|
157
|
-
connectionTimeout: 15000,
|
|
158
|
-
connectionPingInterval: 2000,
|
|
159
|
-
maxConnectionAttempts: 5,
|
|
160
|
-
autoReconnect: true,
|
|
161
|
-
heartbeatInterval: 30000
|
|
162
|
-
})
|
|
198
|
+
const wio = wioRef.current = new WIO( wioOptions )
|
|
163
199
|
|
|
164
200
|
wio
|
|
165
201
|
.on('connect', async () => {
|
|
@@ -168,11 +204,11 @@ export default forwardRef<MSIRef, MSIProps>( ( props, ref ) => {
|
|
|
168
204
|
* Resolve the token here rather than at mount, so a reconnect after a
|
|
169
205
|
* backgrounded app or a dropped bridge binds with a current token.
|
|
170
206
|
*/
|
|
171
|
-
const { getAccessToken, onReady, onError, onLoaded, style, ...config } =
|
|
207
|
+
const { getAccessToken, onReady, onError, onLoaded, style, ...config } = propsRef.current
|
|
172
208
|
|
|
173
209
|
await wio.emitAsync('bind', {
|
|
174
210
|
...config,
|
|
175
|
-
accessToken: resolveAccessToken(
|
|
211
|
+
accessToken: resolveAccessToken( propsRef.current ),
|
|
176
212
|
origin: 'react-native'
|
|
177
213
|
}, BIND_TIMEOUT )
|
|
178
214
|
|
|
@@ -182,7 +218,7 @@ export default forwardRef<MSIRef, MSIProps>( ( props, ref ) => {
|
|
|
182
218
|
})
|
|
183
219
|
.on('ready', () => {
|
|
184
220
|
setIsReady( true )
|
|
185
|
-
|
|
221
|
+
propsRef.current.onReady?.()
|
|
186
222
|
})
|
|
187
223
|
.on('disconnect', () => {
|
|
188
224
|
setIsConnected( false )
|
|
@@ -201,19 +237,21 @@ export default forwardRef<MSIRef, MSIProps>( ( props, ref ) => {
|
|
|
201
237
|
})
|
|
202
238
|
|
|
203
239
|
/**
|
|
204
|
-
*
|
|
205
|
-
*
|
|
240
|
+
* Re-establish the bridge on resume -- iOS in particular tears down the
|
|
241
|
+
* WebView's socket while the app is backgrounded.
|
|
242
|
+
*
|
|
243
|
+
* This used to also emit `app:background` / `app:foreground`, which read
|
|
244
|
+
* like the gateway paused its work. It never did: the gateway has no
|
|
245
|
+
* handler for either, and its own allowlist dropped them before they got
|
|
246
|
+
* that far. Nor should there be a blanket one -- a rider streaming their
|
|
247
|
+
* position must keep streaming with the phone in their pocket, so whether
|
|
248
|
+
* backgrounding stops anything is the host's call, made through
|
|
249
|
+
* `controls.untrackLiveLocation()` and its counterpart.
|
|
206
250
|
*/
|
|
207
251
|
const subscription = AppState.addEventListener('change', ( state: AppStateStatus ) => {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if( state === 'active' ){
|
|
214
|
-
wio.emit('app:foreground')
|
|
215
|
-
!wio.isConnected() && initializeConnection()
|
|
216
|
-
}
|
|
252
|
+
state === 'active'
|
|
253
|
+
&& !wio.isConnected()
|
|
254
|
+
&& initializeConnection()
|
|
217
255
|
})
|
|
218
256
|
|
|
219
257
|
return () => {
|
|
@@ -231,16 +269,16 @@ export default forwardRef<MSIRef, MSIProps>( ( props, ref ) => {
|
|
|
231
269
|
|
|
232
270
|
const
|
|
233
271
|
chn = wioRef.current,
|
|
234
|
-
controls = new Controls( chn,
|
|
235
|
-
handles = new Handles( chn, controls,
|
|
236
|
-
plugins = new Plugins( chn, handles, controls,
|
|
272
|
+
controls = new Controls( chn, propsRef.current ),
|
|
273
|
+
handles = new Handles( chn, controls, propsRef.current ),
|
|
274
|
+
plugins = new Plugins( chn, handles, controls, propsRef.current )
|
|
237
275
|
|
|
238
276
|
plugins.mount( REGISTERED_PLUGINS )
|
|
239
277
|
|
|
240
278
|
apiRef.current = { controls, handles, plugins }
|
|
241
279
|
isInitializedRef.current = true
|
|
242
280
|
|
|
243
|
-
|
|
281
|
+
propsRef.current.onLoaded?.( apiRef.current )
|
|
244
282
|
}, [ isConnected, isReady ] )
|
|
245
283
|
|
|
246
284
|
const onMessage = ( event: any ) => {
|
|
@@ -274,7 +312,7 @@ export default forwardRef<MSIRef, MSIProps>( ( props, ref ) => {
|
|
|
274
312
|
style={styles.webview}
|
|
275
313
|
injectedJavaScript={`
|
|
276
314
|
${props.env === 'dev' ? injectedConsole() : ''}
|
|
277
|
-
${
|
|
315
|
+
${injectedBridge}
|
|
278
316
|
`}
|
|
279
317
|
javaScriptEnabled
|
|
280
318
|
domStorageEnabled
|
|
@@ -287,6 +325,7 @@ export default forwardRef<MSIRef, MSIProps>( ( props, ref ) => {
|
|
|
287
325
|
bounces={false}
|
|
288
326
|
scrollEnabled={false}
|
|
289
327
|
originWhitelist={[ baseURL ]}
|
|
328
|
+
onShouldStartLoadWithRequest={( { url }: any ) => isGatewayURL( url )}
|
|
290
329
|
onMessage={onMessage}
|
|
291
330
|
onLoadEnd={initializeConnection}
|
|
292
331
|
onError={( { nativeEvent }: any ) => reportError( new Error( nativeEvent?.description || 'WebView error') )}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a URL belongs to the MSI gateway itself.
|
|
3
|
+
*
|
|
4
|
+
* React Native's `originWhitelist` is prefix-matched and unanchored at the
|
|
5
|
+
* end, so a whitelist of `https://msi.dedot.io` also admits
|
|
6
|
+
* `https://msi.dedot.io.example.com`. That matters more in a WebView than it
|
|
7
|
+
* would in a browser: the injected bridge — and the auth secret baked into
|
|
8
|
+
* it — runs in whatever page the WebView ends up on. So the origin has to
|
|
9
|
+
* match exactly, and only a path, query or fragment separator may follow it.
|
|
10
|
+
*
|
|
11
|
+
* @param baseURL - Gateway origin, without a trailing slash
|
|
12
|
+
* @param url - URL the WebView is asking to navigate to
|
|
13
|
+
* @return - Whether the navigation is to the gateway
|
|
14
|
+
*/
|
|
15
|
+
export default function belongsToGateway( baseURL: string, url: string ): boolean {
|
|
16
|
+
if( !url || !baseURL ) return false
|
|
17
|
+
if( url === 'about:blank' || url === baseURL ) return true
|
|
18
|
+
|
|
19
|
+
return [ '/', '?', '#' ].some( separator => url.startsWith( baseURL + separator ) )
|
|
20
|
+
}
|