zhongrj
2025-11-24 276323dce9613867abb3f58a4cc2abbfb2fd0dea
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
import SystemJS from 'SystemJS';
 
export default class ApiFactory{
  // @param events {EventEmitter}
  constructor(events){
    this.events = events; 
  }
 
  // @param api {Object}
  create(api){
    // Adds two functions to obj
    // - eventName
    // - triggerEventName
    // We could just use events, but methods
    // are more robust as we can detect more easily if 
    // things break
 
    const addEndpoint = (obj, eventName, preTrigger = () => {}) => {
      const emitResponse = response => {
        // Timeout needed for modules that have no dependencies
        // and load synchronously. Gives time to setup the listeners.
        setTimeout(() => {
          this.events.emit(`${api.namespace}::${eventName}::Response`, response);
        }, 0);
      };
 
      obj[eventName] = (callbackOrDeps, callbackOrUndef) => {
        if (Array.isArray(callbackOrDeps)){
          // Deps
          // Load dependencies, then raise event as usual
          // by appending the dependencies to the argument list
          this.events.addListener(`${api.namespace}::${eventName}`, args => {
            Promise.all(callbackOrDeps.map(dep => SystemJS.import(dep)))
              .then((...deps) => {
                
                // For each dependency, see if it exports a default module (ES6 style)
                // if it does, export just the default module, otherwise export all modules
                deps = deps.map(dep => {
                    return dep.map(exp => exp.default ? exp.default : exp);
                });
 
                const response = {
                  result: callbackOrUndef(...(Array.from([args]).concat(...deps))),
                  placeholder: args._placeholder
                };
                emitResponse(response);
              });
            });
        }else{
          // Callback
          this.events.addListener(`${api.namespace}::${eventName}`, args => {
            const response = {
              result: callbackOrDeps(args),
              placeholder: args._placeholder
            };
            emitResponse(response);
          });
        }
      }
 
      const triggerEventName = "trigger" + eventName[0].toUpperCase() + eventName.slice(1);
 
      obj[triggerEventName] = (args, responseCb) => {
        if (!args) args = {};
        args._placeholder = {};
        
        preTrigger(args, responseCb);
        if (responseCb){
          this.events.addListener(`${api.namespace}::${eventName}::Response`, response => {
            // Give time to all listeners to receive the replies
            // then remove the listener to avoid sending duplicate responses
            const curSub = this.events._currentSubscription;
 
            setTimeout(() => {
              curSub.remove();
            }, 0);
 
            if (response.placeholder === args._placeholder) responseCb(response.result);
          });
        }
        this.events.emit(`${api.namespace}::${eventName}`, args);
      };
    }
 
    let obj = {};
    api.endpoints.forEach(endpoint => {
      if (!Array.isArray(endpoint)) endpoint = [endpoint];
      addEndpoint(obj, ...endpoint);
    });
 
    if (api.helpers){
      obj = Object.assign(obj, api.helpers);
    }
 
    // Handle syncronous function on/off/export
    (api.functions || []).forEach(func => {
      let callbacks = [];
      obj[func] = (...args) => {
        for (let i = 0; i < callbacks.length; i++){
          if ((callbacks[i])(...args)) return true;
        }
        return false;
      };
 
      const onName = "on" + func[0].toUpperCase() + func.slice(1);
      const offName = "off" + func[0].toUpperCase() + func.slice(1);
      obj[onName] = f => {
        callbacks.push(f);
      };
      obj[offName] = f => {
        callbacks = callbacks.filter(cb => cb !== f);
      };
    });
 
    return obj;
  }
 
}