UNPKG

torii

Version:

A set of clean abstractions for authentication in Ember.js

274 lines (216 loc) 6.57 kB
/* * Modification of Stefan Penner's StateMachine.js: https://github.com/stefanpenner/state_machine.js/ * * This modification requires Ember.js to be loaded first */ var a_slice = Array.prototype.slice; var o_keys = Object.keys; function makeArray(entry){ if (entry.constructor === Array) { return entry; }else if(entry) { return [entry]; }else{ return []; } } function StateMachine(options){ var initialState = options.initialState; this.states = options.states; if (!this.states) { throw new Error('StateMachine needs states'); } this.state = this.states[initialState]; if (!this.state) { throw new Error('Missing initial state'); } this.currentStateName = initialState; this._subscriptions = {}; var beforeTransitions = (options.beforeTransitions ||[]); var afterTransitions = (options.afterTransitions ||[]); var rule; var i, length; for(i = 0, length = beforeTransitions.length; length > i; i++){ rule = beforeTransitions[i]; this.beforeTransition.call(this, rule, rule.fn); } for(i = 0, length = afterTransitions.length; length > i; i++){ rule = afterTransitions[i]; this.afterTransition.call(this, rule, rule.fn); } } var SPLAT = StateMachine.SPLAT = '*'; StateMachine.transitionTo = function(state){ return function(){ this.transitionTo(state); }; }; StateMachine.prototype = { states: {}, toString() { return "<StateMachine currentState:'" + this.currentStateName +"' >"; }, transitionTo(nextStateName) { if (nextStateName.charAt(0) === '.') { var splits = this.currentStateName.split('.').slice(0,-1); // maybe all states should have an implicit leading dot (kinda like dns) if (0 < splits.length){ nextStateName = splits.join('.') + nextStateName; } else { nextStateName = nextStateName.substring(1); } } var state = this.states[nextStateName], stateName = this.currentStateName; if (!state) { throw new Error('Unknown State: `' + nextStateName + '`'); } this.willTransition(stateName, nextStateName); this.state = state; this.currentStateName = nextStateName; this.didTransition(stateName, nextStateName); }, beforeTransition(options, fn) { this._transition('willTransition', options, fn); }, afterTransition(options, fn) { this._transition('didTransition', options, fn); }, _transition(event, filter, fn) { var from = filter.from || SPLAT, to = filter.to || SPLAT, matchingTo, matchingFrom, toSplatOffset, fromSplatOffset, negatedMatchingTo, negatedMatchingFrom; if (to.indexOf('!') === 0) { matchingTo = to.substr(1); negatedMatchingTo = true; } else { matchingTo = to; negatedMatchingTo = false; } if (from.indexOf('!') === 0) { matchingFrom = from.substr(1); negatedMatchingFrom = true; } else { matchingFrom = from; negatedMatchingFrom = false; } fromSplatOffset = matchingFrom.indexOf(SPLAT); toSplatOffset = matchingTo.indexOf(SPLAT); if (fromSplatOffset >= 0) { matchingFrom = matchingFrom.substring(fromSplatOffset, 0); } if (toSplatOffset >= 0) { matchingTo = matchingTo.substring(toSplatOffset, 0); } this.on(event, function(currentFrom, currentTo) { var currentMatcherTo = currentTo, currentMatcherFrom = currentFrom, toMatches, fromMatches; if (fromSplatOffset >= 0){ currentMatcherFrom = currentFrom.substring(fromSplatOffset, 0); } if (toSplatOffset >= 0){ currentMatcherTo = currentTo.substring(toSplatOffset, 0); } toMatches = (currentMatcherTo === matchingTo) !== negatedMatchingTo; fromMatches = (currentMatcherFrom === matchingFrom) !== negatedMatchingFrom; if (toMatches && fromMatches) { fn.call(this, currentFrom, currentTo); } }); }, willTransition(from, to) { this._notify('willTransition', from, to); }, didTransition(from, to) { this._notify('didTransition', from, to); }, _notify(name, from, to) { var subscriptions = (this._subscriptions[name] || []); for( var i = 0, length = subscriptions.length; i < length; i++){ subscriptions[i].call(this, from, to); } }, on(event, fn) { this._subscriptions[event] = this._subscriptions[event] || []; this._subscriptions[event].push(fn); }, off(event, fn) { var idx = this._subscriptions[event].indexOf(fn); if (fn){ if (idx) { this._subscriptions[event].splice(idx, 1); } }else { this._subscriptions[event] = null; } }, send(eventName) { var event = this.state[eventName]; var args = a_slice.call(arguments, 1); if (event) { return event.apply(this, args); } else { this.unhandledEvent(eventName); } }, trySend(eventName) { var event = this.state[eventName]; var args = a_slice.call(arguments,1); if (event) { return event.apply(this, args); } }, event(eventName, callback) { var states = this.states; var eventApi = { transition() { var first = arguments[0], second = arguments[1], events = normalizeEvents(eventName, first, second); o_keys(events).forEach(function(from){ var to = events[from]; compileEvent(states, eventName, from, to, StateMachine.transitionTo(to)); }); } }; callback.call(eventApi); }, unhandledEvent(event) { var message = "Unknown Event: `" + event + "` for: " + this.toString(); throw new Error(message); } }; function normalizeEvents(eventName, first, second){ var events; if (!first) { throw new Error('invalid Transition'); } if (second) { var froms = first, to = second; events = expandArrayEvents(froms, to); } else { if (first.constructor === Object) { events = first; } else { throw new Error('something went wrong'); } } return events; } function expandArrayEvents(froms, to){ return makeArray(froms).reduce(function(events, from){ events[from] = to; return events; }, {}); } function compileEvent(states, eventName, from, to, fn){ var state = states[from]; if (from && to && state) { states[from][eventName] = fn; } else { var message = "invalid transition state: " + (state && state.currentStateName) + " from: " + from+ " to: " + to ; throw new Error(message); } } export default StateMachine;