~cytrogen/masto-fe

ref: 97f3d7b66280f5366a63bb0ad3df47e869530f2d masto-fe/app/javascript/core/remote_interaction_helper.ts -rw-r--r-- 4.3 KiB
97f3d7b6 — Jeong Arm Fix Version metadata have trailing dot (#2403) 2 years ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/*

This script is meant to to be used in an `iframe` with the sole purpose of doing webfinger queries
client-side without being restricted by a strict `connect-src` Content-Security-Policy directive.

It communicates with the parent window through message events that are authenticated by origin,
and performs no other task.

*/

import 'packs/public-path';

import axios from 'axios';

interface JRDLink {
  rel: string;
  template?: string;
  href?: string;
}

const isJRDLink = (link: unknown): link is JRDLink =>
  typeof link === 'object' &&
  link !== null &&
  'rel' in link &&
  typeof link.rel === 'string' &&
  (!('template' in link) || typeof link.template === 'string') &&
  (!('href' in link) || typeof link.href === 'string');

const findLink = (rel: string, data: unknown): JRDLink | undefined => {
  if (
    typeof data === 'object' &&
    data !== null &&
    'links' in data &&
    data.links instanceof Array
  ) {
    return data.links.find(
      (link): link is JRDLink => isJRDLink(link) && link.rel === rel,
    );
  } else {
    return undefined;
  }
};

const findTemplateLink = (data: unknown) =>
  findLink('http://ostatus.org/schema/1.0/subscribe', data)?.template;

const fetchInteractionURLSuccess = (
  uri_or_domain: string,
  template: string,
) => {
  window.parent.postMessage(
    {
      type: 'fetchInteractionURL-success',
      uri_or_domain,
      template,
    },
    window.origin,
  );
};

const fetchInteractionURLFailure = () => {
  window.parent.postMessage(
    {
      type: 'fetchInteractionURL-failure',
    },
    window.origin,
  );
};

const isValidDomain = (value: string) => {
  const url = new URL('https:///path');
  url.hostname = value;
  return url.hostname === value;
};

// Attempt to find a remote interaction URL from a domain
const fromDomain = (domain: string) => {
  const fallbackTemplate = `https://${domain}/authorize_interaction?uri={uri}`;

  axios
    .get(`https://${domain}/.well-known/webfinger`, {
      params: { resource: `https://${domain}` },
    })
    .then(({ data }) => {
      const template = findTemplateLink(data);
      fetchInteractionURLSuccess(domain, template ?? fallbackTemplate);
      return;
    })
    .catch(() => {
      fetchInteractionURLSuccess(domain, fallbackTemplate);
    });
};

// Attempt to find a remote interaction URL from an arbitrary URL
const fromURL = (url: string) => {
  const domain = new URL(url).host;
  const fallbackTemplate = `https://${domain}/authorize_interaction?uri={uri}`;

  axios
    .get(`https://${domain}/.well-known/webfinger`, {
      params: { resource: url },
    })
    .then(({ data }) => {
      const template = findTemplateLink(data);
      fetchInteractionURLSuccess(url, template ?? fallbackTemplate);
      return;
    })
    .catch(() => {
      fromDomain(domain);
    });
};

// Attempt to find a remote interaction URL from a `user@domain` string
const fromAcct = (acct: string) => {
  acct = acct.replace(/^@/, '');

  const segments = acct.split('@');

  if (segments.length !== 2 || !segments[0] || !isValidDomain(segments[1])) {
    fetchInteractionURLFailure();
    return;
  }

  const domain = segments[1];
  const fallbackTemplate = `https://${domain}/authorize_interaction?uri={uri}`;

  axios
    .get(`https://${domain}/.well-known/webfinger`, {
      params: { resource: `acct:${acct}` },
    })
    .then(({ data }) => {
      const template = findTemplateLink(data);
      fetchInteractionURLSuccess(acct, template ?? fallbackTemplate);
      return;
    })
    .catch(() => {
      // TODO: handle host-meta?
      fromDomain(domain);
    });
};

const fetchInteractionURL = (uri_or_domain: string) => {
  if (/^https?:\/\//.test(uri_or_domain)) {
    fromURL(uri_or_domain);
  } else if (uri_or_domain.includes('@')) {
    fromAcct(uri_or_domain);
  } else {
    fromDomain(uri_or_domain);
  }
};

window.addEventListener('message', (event: MessageEvent<unknown>) => {
  // Check message origin
  if (
    !window.origin ||
    window.parent !== event.source ||
    event.origin !== window.origin
  ) {
    return;
  }

  if (
    event.data &&
    typeof event.data === 'object' &&
    'type' in event.data &&
    event.data.type === 'fetchInteractionURL' &&
    'uri_or_domain' in event.data &&
    typeof event.data.uri_or_domain === 'string'
  ) {
    fetchInteractionURL(event.data.uri_or_domain);
  }
});