刘光辉
8 小时以前 cf11e63b659afcc9792ab3e395f1b45d844431a8
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
export const PLUGIN_GUID = 'asc.{C764F7D6-055D-4C7D-97FD-83D2C4440585}';
export const PLUGIN_VERSION = '0.1.0';
export const PLUGIN_BIZ_MODULE = 'dms';
export const PLUGIN_BIZ_SCENE = 'dms.qianpiye';
export const PLUGIN_SCOPE = 'onlyoffice:dms-qianpiye';
 
interface QianpiyePluginContextBase {
  apiBaseUrl: string;
  bizDataId?: string;
  bizModule: typeof PLUGIN_BIZ_MODULE;
  bizScene: typeof PLUGIN_BIZ_SCENE;
  documentId: string;
  pluginVersion: typeof PLUGIN_VERSION;
}
 
export type QianpiyePluginContext = QianpiyePluginContextBase & (
  | { canAnnotate: false }
  | { canAnnotate: true; scope: typeof PLUGIN_SCOPE; ticket: string }
);
 
export type WritableQianpiyePluginContext = Extract<QianpiyePluginContext, { canAnnotate: true }>;
 
function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}
 
function isNonEmptyString(value: unknown): value is string {
  return typeof value === 'string' && value.trim().length > 0;
}
 
function isHttpUrl(value: unknown): value is string {
  if (!isNonEmptyString(value)) return false;
  try {
    const url = new URL(value);
    return ['http:', 'https:'].includes(url.protocol) && !url.username && !url.password && !url.search && !url.hash;
  } catch {
    return false;
  }
}
 
function parseOptions(options: unknown): unknown {
  if (typeof options !== 'string') return options;
  try {
    return JSON.parse(options) as unknown;
  } catch {
    return undefined;
  }
}
 
function resolvePluginOptions(options: unknown): unknown {
  const value = parseOptions(options);
  if (!isRecord(value) || 'apiBaseUrl' in value) return value;
  return parseOptions(value[PLUGIN_GUID]);
}
 
function validationFailure(value: unknown): string | undefined {
  if (!isRecord(value)) return '未收到插件上下文';
  const invalidFields = [
    !isHttpUrl(value.apiBaseUrl) ? 'apiBaseUrl' : '',
    !isNonEmptyString(value.documentId) ? 'documentId' : '',
    value.bizModule !== PLUGIN_BIZ_MODULE ? 'bizModule' : '',
    value.bizScene !== PLUGIN_BIZ_SCENE ? 'bizScene' : '',
    value.pluginVersion !== PLUGIN_VERSION ? 'pluginVersion' : '',
    typeof value.canAnnotate !== 'boolean' ? 'canAnnotate' : '',
    value.bizDataId !== undefined && value.bizDataId !== null && !isNonEmptyString(value.bizDataId)
      ? 'bizDataId'
      : '',
  ].filter(Boolean);
  if (value.canAnnotate === true) {
    if (value.scope !== PLUGIN_SCOPE) invalidFields.push('scope');
    if (!isNonEmptyString(value.ticket)) invalidFields.push('ticket');
  }
  return invalidFields.length > 0 ? `缺少或无效字段:${invalidFields.join(', ')}` : undefined;
}
 
export function parsePluginContext(options: unknown): QianpiyePluginContext {
  const value = resolvePluginOptions(options);
  if (!isRecord(value)) {
    throw new Error('签批页标注插件运行上下文无效(未收到插件上下文)');
  }
  const failure = validationFailure(value);
  if (failure) {
    throw new Error(`签批页标注插件运行上下文无效(${failure})`);
  }
  const apiBaseUrl = value.apiBaseUrl;
  const documentId = value.documentId;
  const bizDataId = value.bizDataId;
  const canAnnotate = value.canAnnotate;
  if (!isHttpUrl(apiBaseUrl) || !isNonEmptyString(documentId) || typeof canAnnotate !== 'boolean') {
    throw new Error('签批页标注插件运行上下文无效(字段校验失败)');
  }
  const base: QianpiyePluginContextBase = {
    apiBaseUrl: apiBaseUrl.replace(/\/+$/u, ''),
    documentId,
    ...(isNonEmptyString(bizDataId) ? { bizDataId } : {}),
    bizModule: PLUGIN_BIZ_MODULE,
    bizScene: PLUGIN_BIZ_SCENE,
    pluginVersion: PLUGIN_VERSION,
  };
  if (!canAnnotate) {
    return { ...base, canAnnotate: false };
  }
  const ticket = value.ticket;
  if (value.scope !== PLUGIN_SCOPE || !isNonEmptyString(ticket)) {
    throw new Error('签批页标注插件运行上下文无效(缺少或无效字段:scope, ticket)');
  }
  return {
    ...base,
    canAnnotate: true,
    scope: PLUGIN_SCOPE,
    ticket,
  };
}